homebrew-cask/developer/bin/casks-without-zap

121 lines
3.8 KiB
Ruby
Executable File

#!/usr/bin/env ruby
# frozen_string_literal: true
# rubocop:disable Style/TopLevelMethodDefinition
require "find"
require "json"
require "open-uri"
require "open3"
require "optparse"
require "pathname"
require "tmpdir"
# Exit cleanup
TMP_DIR = Pathname.new(Dir.mktmpdir).freeze
at_exit { TMP_DIR.rmtree }
# Constants
ONLINE_ISSUE = "https://github.com/Homebrew/homebrew-cask/issues/88469"
CASK_REPOS = %w[homebrew-cask homebrew-cask-versions].freeze
CASK_JSON_URL = "https://formulae.brew.sh/api/analytics/cask-install/365d.json"
# Download the file and save it to the specified directory
File.open("#{TMP_DIR}/cask.json", "wb") do |output_file|
URI.parse(CASK_JSON_URL).open do |input_file|
output_file.write(input_file.read)
end
end
CASK_JSON = File.read("#{TMP_DIR}/cask.json").freeze
CASK_DATA = JSON.parse(CASK_JSON).freeze
# Helpers
def cask_name(cask_path)
cask_path.basename.sub(/\.rb$/, "")
end
def cask_url(tap_dir, cask_path)
tap_base = tap_dir.dirname.basename.to_path
cask_base = cask_path.relative_path_from(tap_dir).to_path
"https://github.com/Homebrew/#{tap_base}/blob/master/Casks/#{cask_base}"
end
def find_count(cask_name, data)
data["items"].find { |item| item["cask"] == cask_name.to_s }&.dig("count") || "0"
end
# Options
ARGV.push("--help") unless ARGV.include?("run")
OptionParser.new do |parser|
parser.banner = <<~BANNER
Generates lists of casks missing `zap` in official repos, and copies it to replace the information on #{ONLINE_ISSUE}
Usage:
#{File.basename($PROGRAM_NAME)} run
The argument 'run' is necessary to prevent running the script by mistake.
BANNER
end.parse!
# Grab all taps and casks
CASK_DIRS = CASK_REPOS.each_with_object([]) do |repo, tap_dirs|
clone_dir = TMP_DIR.join(repo)
casks_dir = clone_dir.join("Casks")
tap_dirs.push(casks_dir)
system("git", "clone", "--depth", "1", "https://github.com/Homebrew/#{repo}.git", clone_dir.to_path)
end.freeze
# Collect all casks from each tap directory into a hash
ALL_CASKS = CASK_DIRS.each_with_object({}) do |tap_dir, casks|
casks[tap_dir] = []
# Recursively find all Ruby files in the tap directory
Find.find(tap_dir) do |path|
# Skip if not a file or not a Ruby file
next unless File.file?(path)
next if File.extname(path) != ".rb"
# Add the path to the casks array for the current tap
casks[tap_dir].push(Pathname.new(path))
end
end.freeze
# Filter casks that are missing a 'zap' stanza and are not discontinued
CASKS_NO_ZAP = ALL_CASKS.each_with_object({}) do |(tap_dir, casks), without_zap|
without_zap[tap_dir] = casks.reject do |file|
# Read file content and check if it contains 'zap' or 'discontinued'
file_content = file.read
file_content.match?(/\s+(# No )?zap /) || file_content.match?(/\s+discontinued /)
end
# Remove the tap directory from the result if it has no casks missing 'zap'
without_zap.delete(tap_dir) if without_zap[tap_dir].empty?
end.freeze
CASK_LISTS = CASKS_NO_ZAP.each_with_object([]) do |(tap_dir, casks), message|
message.push("<details><summary>#{tap_dir.dirname.basename.to_path}</summary>")
message.push("") # Empty line so the markdown still works inside the HTML
message.push("| Cask | Downloads |", "| :--- | ---: |") # Table header
# Sort casks by count
sorted_casks = casks.sort_by { |cask_file| -find_count(cask_name(cask_file), CASK_DATA).delete(",").to_i }
sorted_casks.each do |cask_file|
cask_name = cask_name(cask_file)
count = find_count(cask_name, CASK_DATA)
message.push("| [#{cask_name}](#{cask_url(tap_dir, cask_file)}) | #{count} |")
end
message.push("</details>")
end.freeze
Open3.capture2("/usr/bin/pbcopy", stdin_data: "#{CASK_LISTS.join("\n")}\n")
puts("Copied lists to clipboard. Replace the information in the issue.")
system("open", ONLINE_ISSUE)
# rubocop:enable Style/TopLevelMethodDefinition