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.



1125
1126
1127
1128
1129
1130
# File 'lib/kettle/jem.rb', line 1125

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

#cacheObject (readonly)

Returns the value of attribute cache.



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

def cache
  @cache
end

Instance Method Details

#fetch_gem_info(gem_name, version) ⇒ Object



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

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:



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

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



1167
1168
1169
1170
# File 'lib/kettle/jem.rb', line 1167

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



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

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



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

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



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

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



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

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