end_of_life 0.4.1 → 0.5.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b8e0689d1cdf6da1fa737f8d699a060a8544184dfb345d6af4093e6e5e000c13
4
- data.tar.gz: 608169452246f72b9351b07ae4f243d4ce74c3416c451a5fdf600b97dbc3f529
3
+ metadata.gz: 6b96d774296476963033c1a52121ce717a47a3f20e6463294729b852eb9c9504
4
+ data.tar.gz: 63e2e455e67bae56b2dfe58a848785604f5d88662f317dc11e2e40e4783d4b4e
5
5
  SHA512:
6
- metadata.gz: 57c0401637ca7d60d59d27ab35f4bee948f09bf188df6189a41be14ceb906c50ee77059a550076122f14a3b118c9e6fdd2f10dd520d19b562670c8fe229cf5df
7
- data.tar.gz: 8cf1db7f6007559d3066696e1f3820fdaae1b055654456d47d48d67b6828530315992aae95ac9ea2ecf3927b3a39b80f84df7a762d24cc6c4f073bc2956d648e
6
+ metadata.gz: 7ebbccd62beca6dfd89b4e91ecbaf14c043c7a65d6cb8818018834e439db6212ca62834b414614e1db57872d305e82af8219afdb7cfbdf1bf8c95571cf801be9
7
+ data.tar.gz: 4e9750c6f5e880db427baac01d8d06d1e08dd3afb666249ba19174315d1ae9b0f402e60848fa792bb562ff7fbcb7040a48a03efa2dda0a6344d8a68938edb553
data/CHANGELOG.md CHANGED
@@ -1,5 +1,9 @@
1
1
  ## [Unreleased]
2
2
 
3
+ - 🎉 Add support for scanning EOL Rails versions!
4
+ - Use the `--product=rails` option to scan for Rails instead of Ruby.
5
+ - End of Life looks for an exact Rails version on the `Gemfile` file or `Gemfile.lock`.
6
+
3
7
  ## [0.4.1] - 2025-05-23
4
8
 
5
9
  - [BUGFIX] Handle empty/invalid version files
data/Gemfile CHANGED
@@ -10,6 +10,6 @@ gem "rake", "~> 13.0"
10
10
  gem "rspec", "~> 3.10"
11
11
  gem "rspec-mocks", "~> 3.10"
12
12
  gem "simplecov", "~> 0.22.0"
13
- gem "standard", "~> 1.26"
13
+ gem "standard", github: "testdouble/standard"
14
14
  gem "vcr", "~> 6.0"
15
15
  gem "webmock", "~> 3.13"
data/Gemfile.lock CHANGED
@@ -1,31 +1,43 @@
1
+ GIT
2
+ remote: https://github.com/testdouble/standard.git
3
+ revision: 2a0584ed57122065d0972fe5a55eb83aec150b54
4
+ specs:
5
+ standard (1.50.0)
6
+ language_server-protocol (~> 3.17.0.2)
7
+ lint_roller (~> 1.0)
8
+ rubocop (~> 1.75.5)
9
+ standard-custom (~> 1.0.0)
10
+ standard-performance (~> 1.8)
11
+
1
12
  PATH
2
13
  remote: .
3
14
  specs:
4
- end_of_life (0.4.1)
15
+ end_of_life (0.5.0)
5
16
  async
6
17
  dry-monads (~> 1.3)
7
18
  octokit (~> 9.0)
8
19
  pastel (~> 0.8.0)
9
20
  tty-spinner (~> 0.9.0)
10
21
  tty-table (~> 0.12.0)
22
+ zeitwerk (~> 2.7)
11
23
 
12
24
  GEM
13
25
  remote: https://rubygems.org/
14
26
  specs:
15
27
  addressable (2.8.7)
16
28
  public_suffix (>= 2.0.2, < 7.0)
17
- ast (2.4.2)
18
- async (2.24.0)
29
+ ast (2.4.3)
30
+ async (2.30.0)
19
31
  console (~> 1.29)
20
32
  fiber-annotation
21
- io-event (~> 1.9)
33
+ io-event (~> 1.11)
22
34
  metrics (~> 0.12)
23
- traces (~> 0.15)
35
+ traces (~> 0.18)
24
36
  base64 (0.2.0)
25
37
  bigdecimal (3.1.9)
26
38
  climate_control (1.2.0)
27
39
  concurrent-ruby (1.3.5)
28
- console (1.30.2)
40
+ console (1.34.0)
29
41
  fiber-annotation
30
42
  fiber-local (~> 1.1)
31
43
  json
@@ -38,43 +50,44 @@ GEM
38
50
  concurrent-ruby (~> 1.0)
39
51
  logger
40
52
  zeitwerk (~> 2.6)
41
- dry-monads (1.8.3)
53
+ dry-monads (1.9.0)
42
54
  concurrent-ruby (~> 1.0)
43
55
  dry-core (~> 1.1)
44
56
  zeitwerk (~> 2.6)
45
- faraday (2.13.1)
57
+ faraday (2.13.4)
46
58
  faraday-net_http (>= 2.0, < 3.5)
47
59
  json
48
60
  logger
49
- faraday-net_http (3.4.0)
61
+ faraday-net_http (3.4.1)
50
62
  net-http (>= 0.5.0)
51
63
  fiber-annotation (0.2.0)
52
64
  fiber-local (1.1.0)
53
65
  fiber-storage
54
66
  fiber-storage (1.0.1)
55
67
  hashdiff (1.1.2)
56
- io-event (1.10.1)
68
+ io-event (1.14.0)
57
69
  json (2.9.1)
58
- language_server-protocol (3.17.0.3)
70
+ language_server-protocol (3.17.0.5)
59
71
  lint_roller (1.1.0)
60
72
  logger (1.7.0)
61
- metrics (0.12.2)
73
+ metrics (0.14.0)
62
74
  net-http (0.6.0)
63
75
  uri
64
76
  octokit (9.2.0)
65
77
  faraday (>= 1, < 3)
66
78
  sawyer (~> 0.9)
67
- parallel (1.26.3)
68
- parser (3.3.6.0)
79
+ parallel (1.27.0)
80
+ parser (3.3.9.0)
69
81
  ast (~> 2.4.1)
70
82
  racc
71
83
  pastel (0.8.0)
72
84
  tty-color (~> 0.5)
85
+ prism (1.4.0)
73
86
  public_suffix (6.0.1)
74
87
  racc (1.8.1)
75
88
  rainbow (3.1.1)
76
89
  rake (13.2.1)
77
- regexp_parser (2.10.0)
90
+ regexp_parser (2.11.2)
78
91
  rexml (3.4.0)
79
92
  rspec (3.13.0)
80
93
  rspec-core (~> 3.13.0)
@@ -89,21 +102,24 @@ GEM
89
102
  diff-lcs (>= 1.2.0, < 2.0)
90
103
  rspec-support (~> 3.13.0)
91
104
  rspec-support (3.13.2)
92
- rubocop (1.69.2)
105
+ rubocop (1.75.8)
93
106
  json (~> 2.3)
94
- language_server-protocol (>= 3.17.0)
107
+ language_server-protocol (~> 3.17.0.2)
108
+ lint_roller (~> 1.1.0)
95
109
  parallel (~> 1.10)
96
110
  parser (>= 3.3.0.2)
97
111
  rainbow (>= 2.2.2, < 4.0)
98
112
  regexp_parser (>= 2.9.3, < 3.0)
99
- rubocop-ast (>= 1.36.2, < 2.0)
113
+ rubocop-ast (>= 1.44.0, < 2.0)
100
114
  ruby-progressbar (~> 1.7)
101
115
  unicode-display_width (>= 2.4.0, < 4.0)
102
- rubocop-ast (1.37.0)
103
- parser (>= 3.3.1.0)
104
- rubocop-performance (1.23.0)
105
- rubocop (>= 1.48.1, < 2.0)
106
- rubocop-ast (>= 1.31.1, < 2.0)
116
+ rubocop-ast (1.46.0)
117
+ parser (>= 3.3.7.2)
118
+ prism (~> 1.4)
119
+ rubocop-performance (1.25.0)
120
+ lint_roller (~> 1.1)
121
+ rubocop (>= 1.75.0, < 2.0)
122
+ rubocop-ast (>= 1.38.0, < 2.0)
107
123
  ruby-progressbar (1.13.0)
108
124
  sawyer (0.9.2)
109
125
  addressable (>= 2.3.5)
@@ -114,24 +130,18 @@ GEM
114
130
  simplecov_json_formatter (~> 0.1)
115
131
  simplecov-html (0.13.1)
116
132
  simplecov_json_formatter (0.1.4)
117
- standard (1.43.0)
118
- language_server-protocol (~> 3.17.0.2)
119
- lint_roller (~> 1.0)
120
- rubocop (~> 1.69.1)
121
- standard-custom (~> 1.0.0)
122
- standard-performance (~> 1.6)
123
133
  standard-custom (1.0.2)
124
134
  lint_roller (~> 1.0)
125
135
  rubocop (~> 1.50)
126
- standard-performance (1.6.0)
136
+ standard-performance (1.8.0)
127
137
  lint_roller (~> 1.1)
128
- rubocop-performance (~> 1.23.0)
138
+ rubocop-performance (~> 1.25.0)
129
139
  strings (0.2.1)
130
140
  strings-ansi (~> 0.2)
131
141
  unicode-display_width (>= 1.5, < 3.0)
132
142
  unicode_utils (~> 1.4)
133
143
  strings-ansi (0.2.0)
134
- traces (0.15.2)
144
+ traces (0.18.1)
135
145
  tty-color (0.6.0)
136
146
  tty-cursor (0.7.1)
137
147
  tty-screen (0.8.2)
@@ -162,7 +172,7 @@ DEPENDENCIES
162
172
  rspec (~> 3.10)
163
173
  rspec-mocks (~> 3.10)
164
174
  simplecov (~> 0.22.0)
165
- standard (~> 1.26)
175
+ standard!
166
176
  vcr (~> 6.0)
167
177
  webmock (~> 3.13)
168
178
 
data/README.md CHANGED
@@ -1,6 +1,7 @@
1
1
  # End of Life
2
2
 
3
- This gem lists GitHub repositories using end-of-life Ruby versions.
3
+ This gem lists GitHub repositories using end-of-life versions of various
4
+ products.
4
5
 
5
6
  ![End of Life Demo](demo.gif)
6
7
 
@@ -14,18 +15,20 @@ gem install end_of_life
14
15
 
15
16
  1. Set up a [GitHub access token][] (we recommend using a read-only token);
16
17
 
17
- [github access token]: https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/creating-a-personal-access-token#creating-a-token
18
+ [github access token]:
19
+ https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/creating-a-personal-access-token#creating-a-token
18
20
 
19
- 2. Export the `GITHUB_TOKEN` environment variable or set it when calling `end_of_life`;
21
+ 2. Export the `GITHUB_TOKEN` environment variable or set it when calling
22
+ `end_of_life`;
20
23
 
21
24
  3. Use the `end_of_life` command to list the repositories:
22
25
 
23
26
  ```sh
24
27
  $ GITHUB_TOKEN=something end_of_life # if your platform supports symlinks, you can use the `eol` command instead
25
- [✔] Fetching repositories...
28
+ [✔] Searching repositories with Ruby...
26
29
  [✔] Searching for EOL Ruby in repositories...
27
30
 
28
- Found 2 repositories using EOL Ruby (<= 2.7.8):
31
+ Found 2 repositories using EOL Ruby (<= 3.1.7):
29
32
  ┌───┬──────────────────────────────────────────────┬──────────────┐
30
33
  │ │ Repository │ Ruby version │
31
34
  ├───┼──────────────────────────────────────────────┼──────────────┤
@@ -40,6 +43,7 @@ There are some options to help you filter down the results:
40
43
 
41
44
  ```
42
45
  Usage: end_of_life [options]
46
+ -p, --product NAME Sets the product to scan for (default: ruby). Supported products are: ruby, rails.
43
47
  --exclude=NAME,NAME2 Exclude repositories containing a certain word in its name. You can specify up to five words.
44
48
  --public-only Searches only public repositories
45
49
  --private-only Searches only private repositories
@@ -54,17 +58,19 @@ Usage: end_of_life [options]
54
58
 
55
59
  ## How it works
56
60
 
57
- This gem fetches all your GitHub repositories that contain Ruby code, then
58
- searches for files that may have a Ruby version. Currently, those files are:
59
- `.ruby-version`, `Gemfile`, `Gemfile.lock`, and `.tool-version`. We parse these
60
- files and extract the minimum Ruby version used in the repository.
61
+ This gem fetches all your GitHub repositories that contain code for the
62
+ specified product, then searches for files that may contain version information.
63
+ For Ruby, those files are: `.ruby-version`, `Gemfile`, `Gemfile.lock`, and
64
+ `.tool-version`. End of Life parses these files and extracts the minimum version
65
+ used in the repository.
61
66
 
62
- The EOL Ruby version is provided by https://endoflife.date/, with a file
67
+ The EOL version information is provided by https://endoflife.date/, with a file
63
68
  [fallback].
64
69
 
65
- > **IMPORTANT:** To parse Gemfiles, we need to execute the code inside it. **Be
66
- > careful** because this may be a security risk. We plan to add a secure parser
67
- > for Gemfiles in the future.
70
+ > [!ATTENTION]
71
+ > To parse Gemfiles, we need to execute the code inside them.
72
+ > **Be careful** because this may be a security risk. We plan to add secure
73
+ > parsers for these files in the future.
68
74
 
69
75
  Some other limitations are listed on the [issues page].
70
76
 
@@ -86,7 +92,10 @@ git commits and the created tag, and push the `.gem` file to
86
92
  ## Contributing
87
93
 
88
94
  Bug reports and pull requests are welcome on GitHub at
89
- https://github.com/MatheusRich/end_of_life.
95
+ https://github.com/MatheusRich/end_of_life. If you want to add a new product,
96
+ [check out this commit for reference].
97
+
98
+ [check out this commit for reference]: ba9a92a690e0d61ea09e508c1cd76b8309fb89df
90
99
 
91
100
  ## License
92
101
 
data/end_of_life.gemspec CHANGED
@@ -8,9 +8,9 @@ Gem::Specification.new do |spec|
8
8
  spec.authors = ["Matheus Richard"]
9
9
  spec.email = ["matheusrichardt@gmail.com"]
10
10
 
11
- spec.summary = "Lists repositories using end-of-life Ruby versions."
11
+ spec.summary = "Lists repositories using end-of-life software"
12
12
  spec.description = "Searches your GitHub repositores and lists the ones using end-of-life, i.e. " \
13
- "unmaintained, Ruby versions."
13
+ "unmaintained, software versions."
14
14
  spec.homepage = "https://github.com/MatheusRich/end_of_life"
15
15
  spec.license = "MIT"
16
16
  spec.required_ruby_version = ">= 3.2.0"
@@ -38,6 +38,7 @@ Gem::Specification.new do |spec|
38
38
  spec.add_dependency "pastel", "~> 0.8.0"
39
39
  spec.add_dependency "tty-spinner", "~> 0.9.0"
40
40
  spec.add_dependency "tty-table", "~> 0.12.0"
41
+ spec.add_dependency "zeitwerk", "~> 2.7"
41
42
 
42
43
  # For more information and examples about making a new gem, check out our
43
44
  # guide at: https://bundler.io/guides/creating_gem.html
@@ -0,0 +1,17 @@
1
+ module EndOfLife
2
+ module API
3
+ extend self
4
+
5
+ BASE_URL = "https://endoflife.date/api/v1/"
6
+
7
+ def fetch_product(product) = get("products/#{product}")
8
+
9
+ private
10
+
11
+ def get(path)
12
+ response = Net::HTTP.get(URI("#{BASE_URL}#{path}"))
13
+
14
+ JSON.parse(response, symbolize_names: true)
15
+ end
16
+ end
17
+ end
@@ -1,6 +1,6 @@
1
1
  module EndOfLife
2
2
  class CLI
3
- include TerminalHelper
3
+ include Helpers::Terminal
4
4
 
5
5
  def call(argv)
6
6
  parse_options(argv)
@@ -9,6 +9,10 @@ module EndOfLife
9
9
 
10
10
  private
11
11
 
12
+ def parse_options(argv)
13
+ Options.from(argv)
14
+ end
15
+
12
16
  def execute_command(options)
13
17
  case options[:command]
14
18
  when :help
@@ -16,65 +20,10 @@ module EndOfLife
16
20
  when :version
17
21
  puts "end_of_life v#{EndOfLife::VERSION}"
18
22
  when :print_error
19
- puts error_msg(options[:error])
20
- exit(-1)
23
+ abort error_msg(options[:error])
21
24
  else
22
- check_eol_ruby_on_repositories(options)
25
+ Scanner.scan(options)
23
26
  end
24
27
  end
25
-
26
- def check_eol_ruby_on_repositories(options)
27
- fetch_repositories(options)
28
- .fmap { |repositories| filter_repositories_with_end_of_life(repositories, max_eol_date: options[:max_eol_date]) }
29
- .fmap { |repositories| print_diagnose_for(repositories, max_eol_date: options[:max_eol_date]) }
30
- .or { |error| puts "\n#{error_msg(error)}" }
31
- end
32
-
33
- def parse_options(argv)
34
- Options.from(argv)
35
- end
36
-
37
- def fetch_repositories(options)
38
- with_loading_spinner("Fetching repositories...") do |spinner|
39
- result = Repository.fetch(options)
40
-
41
- spinner.error if result.failure?
42
-
43
- result
44
- end
45
- end
46
-
47
- def filter_repositories_with_end_of_life(repositories, max_eol_date:)
48
- with_loading_spinner("Searching for EOL Ruby in repositories...") do
49
- Sync do
50
- repositories
51
- .tap { |repos| repos.map { |repo| Async { repo.ruby_version } }.map(&:wait) }
52
- .filter { |repo| repo.eol_ruby?(at: max_eol_date) }
53
- end
54
- end
55
- end
56
-
57
- def print_diagnose_for(repositories, max_eol_date:)
58
- puts
59
-
60
- if repositories.empty?
61
- puts "No repositories using EOL Ruby."
62
- return
63
- end
64
-
65
- word = (repositories.size == 1) ? "repository" : "repositories"
66
- puts "Found #{repositories.size} #{word} using EOL Ruby (<= #{RubyVersion.latest_eol(at: max_eol_date)}):"
67
- puts end_of_life_table(repositories)
68
- exit(-1)
69
- end
70
-
71
- def end_of_life_table(repositories)
72
- headers = ["", "Repository", "Ruby version"]
73
- rows = repositories.map.with_index(1) do |repo, i|
74
- [i, repo.url, repo.ruby_version]
75
- end
76
-
77
- table(headers, rows)
78
- end
79
28
  end
80
29
  end
@@ -0,0 +1,18 @@
1
+ require "bundler"
2
+
3
+ module EndOfLife
4
+ module Helpers
5
+ module SilentBundler
6
+ extend self
7
+
8
+ def silence_bundler
9
+ previous_ui = Bundler.ui
10
+ Bundler.ui = Bundler::UI::Silent.new
11
+
12
+ yield
13
+ ensure
14
+ Bundler.ui = previous_ui
15
+ end
16
+ end
17
+ end
18
+ end
@@ -3,7 +3,7 @@ require "tty-spinner"
3
3
  require "tty-table"
4
4
 
5
5
  module EndOfLife
6
- module TerminalHelper
6
+ module Helpers::Terminal
7
7
  def error_msg(message, label: "[ERROR]")
8
8
  label = paint.red("#{label} ")
9
9
 
@@ -0,0 +1,3 @@
1
+ module EndOfLife
2
+ InMemoryFile = Data.define(:path, :read)
3
+ end
@@ -3,50 +3,54 @@ require "optparse"
3
3
  module EndOfLife
4
4
  module Options
5
5
  def self.from(argv)
6
- options = {max_eol_date: Date.today, skip_archived: true}
6
+ options = {product: Product.find("ruby"), max_eol_date: Date.today, skip_archived: true}
7
+ OptionParser.new do |parser|
8
+ options[:parser] = parser
7
9
 
8
- OptionParser.new do |opts|
9
- options[:parser] = opts
10
+ parser.banner = "Usage: end_of_life [options]"
10
11
 
11
- opts.banner = "Usage: end_of_life [options]"
12
+ product_names = EndOfLife.products.map(&:name)
13
+ parser.on("-p NAME", "--product NAME", /#{product_names.join("|")}/i, "Sets the product to scan for (default: ruby). Supported products are: #{product_names.join(", ")}.") do |name|
14
+ options[:product] = Product.find(name)
15
+ end
12
16
 
13
- opts.on("--exclude=NAME,NAME2", Array, "Exclude repositories containing a certain word in its name. You can specify up to five words.") do |excludes|
17
+ parser.on("--exclude=NAME,NAME2", Array, "Exclude repositories containing a certain word in its name. You can specify up to five words.") do |excludes|
14
18
  options[:excludes] = excludes.first(5)
15
19
  end
16
20
 
17
- opts.on("--public-only", "Searches only public repositories") do
21
+ parser.on("--public-only", "Searches only public repositories") do
18
22
  options[:visibility] = :public
19
23
  end
20
24
 
21
- opts.on("--private-only", "Searches only private repositories") do
25
+ parser.on("--private-only", "Searches only private repositories") do
22
26
  options[:visibility] = :private
23
27
  end
24
28
 
25
- opts.on("--repo=USER/REPO", "--repository=USER/REPO", "Searches a specific repository") do |repository|
29
+ parser.on("--repo=USER/REPO", "--repository=USER/REPO", "Searches a specific repository") do |repository|
26
30
  options[:repository] = repository
27
31
  end
28
32
 
29
- opts.on("--org=ORG,ORG2...", "--organization=ORG,ORG2", Array, "Searches within specific organizations") do |organizations|
33
+ parser.on("--org=ORG,ORG2...", "--organization=ORG,ORG2", Array, "Searches within specific organizations") do |organizations|
30
34
  options[:organizations] = organizations
31
35
  end
32
36
 
33
- opts.on("-u NAME", "--user=NAME", "Sets the user used on the repository search") do |user|
37
+ parser.on("-u NAME", "--user=NAME", "Sets the user used on the repository search") do |user|
34
38
  options[:user] = user
35
39
  end
36
40
 
37
- opts.on("--max-eol-days-away NUMBER", "Sets the maximum number of days away a version can be from EOL. It defaults to 0.") do |days|
41
+ parser.on("--max-eol-days-away NUMBER", "Sets the maximum number of days away a version can be from EOL. It defaults to 0.") do |days|
38
42
  options[:max_eol_date] = Date.today + days.to_i.abs
39
43
  end
40
44
 
41
- opts.on("--include-archived", "Includes archived repositories on the search") do
45
+ parser.on("--include-archived", "Includes archived repositories on the search") do
42
46
  options[:skip_archived] = false
43
47
  end
44
48
 
45
- opts.on("-v", "--version", "Displays end_of_life version") do
49
+ parser.on("-v", "--version", "Displays end_of_life version") do
46
50
  options[:command] = :version
47
51
  end
48
52
 
49
- opts.on("-h", "--help", "Displays this help") do
53
+ parser.on("-h", "--help", "Displays this help") do
50
54
  options[:command] = :help
51
55
  end
52
56
  end.parse!(argv)
@@ -0,0 +1,31 @@
1
+ module EndOfLife
2
+ module Parsers
3
+ module Gemfile
4
+ extend Helpers::SilentBundler
5
+ extend self
6
+
7
+ def parse(file_content)
8
+ with_temp_gemfile(file_content) do |gemfile|
9
+ silence_bundler do
10
+ # This is security problem, since it runs the code inside the file
11
+ Bundler::Definition.build(gemfile.path, nil, {})
12
+ end
13
+ end
14
+ end
15
+
16
+ private
17
+
18
+ def with_temp_gemfile(contents)
19
+ # Bundler requires a file to parse, so we need to create a temporary file
20
+ Tempfile.create("tempGemfile") do |tempfile|
21
+ tempfile.write(contents)
22
+ tempfile.rewind
23
+
24
+ yield(tempfile)
25
+ rescue Bundler::BundlerError
26
+ nil # NOTE: maybe a Null object would be cleaner
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,14 @@
1
+ module EndOfLife
2
+ module Parsers
3
+ module GemfileLock
4
+ extend Helpers::SilentBundler
5
+ extend self
6
+
7
+ def parse(file_content)
8
+ silence_bundler do
9
+ Bundler::LockfileParser.new(file_content)
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,11 @@
1
+ module EndOfLife
2
+ module Parsers
3
+ module RubyVersion
4
+ extend self
5
+
6
+ def parse(file_content)
7
+ Gem::Version.new(file_content.strip.delete_prefix("ruby-"))
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,23 @@
1
+ module EndOfLife
2
+ module Parsers
3
+ module ToolVersions
4
+ extend self
5
+
6
+ def parse(file_content)
7
+ file_content
8
+ .lines
9
+ .filter_map { |line|
10
+ line = line.strip
11
+ next if line.start_with?("#") || line.empty?
12
+
13
+ tool, version, * = line.split
14
+
15
+ next if version == "latest"
16
+
17
+ [tool, Gem::Version.new(version)]
18
+ }
19
+ .to_h
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,23 @@
1
+ module EndOfLife
2
+ module Product::Registry
3
+ def scans_for(product_name, search_query:)
4
+ product_registry[product_name.to_sym.downcase] = Product.new(
5
+ product_name,
6
+ search_query,
7
+ version_detector_for(product_name)
8
+ )
9
+ end
10
+
11
+ def find_product(name) = product_registry.fetch(name.to_sym.downcase)
12
+
13
+ def products = product_registry.values
14
+
15
+ private
16
+
17
+ def product_registry
18
+ @@product_registry ||= {}
19
+ end
20
+
21
+ def version_detector_for(product) = VersionDetectors.for_product(product)
22
+ end
23
+ end
@@ -0,0 +1,30 @@
1
+ module EndOfLife
2
+ class Product
3
+ class Release < Data.define(:product, :version, :eol_date)
4
+ include Comparable
5
+
6
+ def self.ruby(version, eol_date: nil) = new(product: "ruby", version:, eol_date:)
7
+
8
+ def initialize(product:, version:, eol_date: nil)
9
+ product = Product.find(product.to_s)
10
+ eol_date = Date.parse(eol_date.to_s) if eol_date
11
+ super(product:, eol_date:, version: Gem::Version.new(version))
12
+ end
13
+
14
+ def eol?(at: Date.today)
15
+ if eol_date
16
+ eol_date <= at
17
+ else
18
+ self <= product.latest_eol_release(at: at)
19
+ end
20
+ end
21
+
22
+ ZERO = Gem::Version.new("0")
23
+ def zero? = version == ZERO
24
+
25
+ def <=>(other) = version <=> other.version
26
+
27
+ def to_s = version.to_s
28
+ end
29
+ end
30
+ end