Skip to content

Instantly share code, notes, and snippets.

@flavorjones
Last active July 14, 2021 14:59
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save flavorjones/6f87e0359af55e033f5a7e8bb5796d75 to your computer and use it in GitHub Desktop.
Save flavorjones/6f87e0359af55e033f5a7e8bb5796d75 to your computer and use it in GitHub Desktop.
Find transitive dependencies of a gem, sort by number of downloads
#! /usr/bin/env ruby
#
# find transitive reverse dependencies of nokogiri with more than a million downloads: "rdeps.rb nokogiri 1000000"
#
require "bundler/inline"
gemfile do
gem "json"
gem "ruby-progressbar"
end
require "json"
require "open-uri"
require "ruby-progressbar"
require "set"
class ReverseDependencyCache
CACHE_FILE = "ReverseDependency.cache"
attr :name, :limit, :traversed
def initialize(name, limit)
@name = name
@limit = limit
@traversed = Set.new
end
def cache
@cache ||= if File.exist?(CACHE_FILE)
JSON.parse(File.read(CACHE_FILE))
else
{}
end
end
def downloads(gem_name)
cache[gem_name] ||= gem_info(gem_name)["downloads"]
end
def checkpoint
FileUtils.cp(CACHE_FILE, "#{CACHE_FILE}.bak") if File.exist?(CACHE_FILE)
File.open(CACHE_FILE, "w") { |f| f.write cache.to_json }
end
def populate_cache
n_layer = 0
layer = Set.new
layer.merge(rdep_info(name))
while !layer.empty?
next_layer = Set.new
pb = ProgressBar.create(total: layer.length,
title: "reverse dependency layer #{n_layer}",
format: "%c of %C in %t %B %e",
output: $stderr)
layer.to_a.sort.each_with_index do |rdep, j|
if downloads(rdep) >= limit
next_layer = next_layer.merge(rdep_info(rdep)) - layer - traversed
pb.log "... #{rdep} #{downloads(rdep)} (next layer will be #{next_layer.size})"
end
traversed.add(rdep)
pb.increment
checkpoint if j % 10 == 0
end
n_layer += 1
layer = next_layer
end
end
def report
puts
cache.find_all { |k,v| v >= limit && traversed.member?(k) }.sort_by { |k,v| -v }.each do |k,v|
puts "#{k}: #{v}"
end
end
private
def gem_info(gem_name)
JSON.parse(URI.open(sprintf("https://rubygems.org/api/v1/gems/%s.json", gem_name)).read)
end
def rdep_info(gem_name)
JSON.parse(URI.open(sprintf("https://rubygems.org/api/v1/gems/%s/reverse_dependencies.json", gem_name)).read).uniq
end
end
rdc = ReverseDependencyCache.new(ARGV[0], ARGV[1].to_i)
rdc.populate_cache
rdc.report
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment