Class: Kettle::Jem::RubyGemsResolver

Inherits:
Object
  • Object
show all
Defined in:
lib/kettle/jem.rb

Constant Summary collapse

RUBYGEMS_V1_API_BASE =
"https://gem.coop/api/v1"
RUBYGEMS_V2_API_BASE =
"https://gem.coop/api/v2/rubygems"

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(cache: {}, http_get: nil, v1_api_base: RUBYGEMS_V1_API_BASE, v2_api_base: RUBYGEMS_V2_API_BASE) ⇒ RubyGemsResolver

Returns a new instance of RubyGemsResolver.



1123
1124
1125
1126
1127
1128
# File 'lib/kettle/jem.rb', line 1123

def initialize(cache: {}, http_get: nil, v1_api_base: RUBYGEMS_V1_API_BASE, v2_api_base: RUBYGEMS_V2_API_BASE)
  @cache = cache
  @http_get = http_get || ->(uri) { Net::HTTP.get_response(uri) }
  @v1_api_base = v1_api_base.to_s.delete_suffix("/")
  @v2_api_base = v2_api_base.to_s.delete_suffix("/")
end

Instance Attribute Details

#cache ⇒ Object (readonly)

Returns the value of attribute cache.



1121
1122
1123
# File 'lib/kettle/jem.rb', line 1121

def cache
  @cache
end

Instance Method Details

#fetch_gem_info(gem_name, version) ⇒ Object



1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
# File 'lib/kettle/jem.rb', line 1199

def fetch_gem_info(gem_name, version)
  cache_key = "info:#{gem_name}:#{version}"
  return cache.fetch(cache_key) if cache.key?(cache_key)

  uri = URI("#{@v2_api_base}/#{escape_path_component(gem_name)}/versions/#{escape_path_component(version)}.json")
  response = @http_get.call(uri)
  return unless successful_response?(response)

  cache[cache_key] = JSON.parse(response_body(response))
end

#fetch_versions(gem_name) ⇒ Object

Raises:



1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
# File 'lib/kettle/jem.rb', line 1188

def fetch_versions(gem_name)
  cache_key = "versions:#{gem_name}"
  return cache.fetch(cache_key) if cache.key?(cache_key)

  uri = URI("#{@v1_api_base}/versions/#{escape_path_component(gem_name)}.json")
  response = @http_get.call(uri)
  raise Error, "RubyGems API error for #{gem_name}: #{response_code(response)}" unless successful_response?(response)

  cache[cache_key] = JSON.parse(response_body(response)).sort_by { |entry| Gem::Version.new(entry.fetch("number")) }
end

#min_ruby_version(gem_name, version) ⇒ Object



1165
1166
1167
1168
# File 'lib/kettle/jem.rb', line 1165

def min_ruby_version(gem_name, version)
  entry = fetch_versions(gem_name).find { |candidate| candidate["number"].to_s == version.to_s }
  parse_min_ruby(entry && entry["ruby_version"])
end

#minor_versions_by_major(gem_name, requirements: nil) ⇒ Object



1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
# File 'lib/kettle/jem.rb', line 1170

def minor_versions_by_major(gem_name, requirements: nil)
  versions(gem_name, requirements: requirements).each_with_object({}) do |entry, grouped|
    version = Gem::Version.new(entry.fetch(:number))
    segments = version.segments
    next unless segments[0]

    major = segments[0]
    minor = "#{segments[0]}.#{segments[1] || 0}"
    grouped[major] ||= Set.new
    grouped[major] << minor
  end.sort_by(&:first).map do |major, minors|
    {
      major: major,
      minors: minors.to_a.sort_by { |minor| Gem::Version.new(minor) }
    }
  end
end

#parse_min_ruby(requirement) ⇒ Object



1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
# File 'lib/kettle/jem.rb', line 1210

def parse_min_ruby(requirement)
  return if requirement.to_s.strip.empty?

  parsed = Gem::Requirement.new(requirement.to_s)
  parsed.requirements.each do |operator, version|
    return version if operator == ">="
  end
  parsed.requirements.each do |operator, version|
    return version if operator == "~>"
  end
  nil
rescue ArgumentError
  nil
end

#version_info(gem_name, version) ⇒ Object



1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
# File 'lib/kettle/jem.rb', line 1147

def version_info(gem_name, version)
  data = fetch_gem_info(gem_name, version)
  return unless data

  runtime_dependencies = Array(data.dig("dependencies", "runtime")).map do |dependency|
    {
      name: dependency["name"],
      requirements: dependency["requirements"]
    }
  end

  {
    number: data["number"] || version.to_s,
    ruby_version: data["ruby_version"],
    runtime_dependencies: runtime_dependencies
  }
end

#versions(gem_name, include_prerelease: false, requirements: nil) ⇒ Object



1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
# File 'lib/kettle/jem.rb', line 1130

def versions(gem_name, include_prerelease: false, requirements: nil)
  requirement = normalize_requirements(requirements)
  fetch_versions(gem_name).filter_map do |entry|
    number = entry["number"].to_s
    next if number.empty?
    next if !include_prerelease && entry["prerelease"]
    next if requirement && !requirement.satisfied_by?(Gem::Version.new(number))

    {
      number: number,
      ruby_version: entry["ruby_version"],
      created_at: entry["created_at"],
      prerelease: !!entry["prerelease"]
    }
  end.sort_by { |entry| Gem::Version.new(entry.fetch(:number)) }
end