Bump some utils/ files to Sorbet typed: strict

This commit is contained in:
Issy Long 2025-01-11 21:36:59 +00:00
parent 55475cb11a
commit 975a707b3c
No known key found for this signature in database
6 changed files with 194 additions and 83 deletions

View File

@ -1,4 +1,4 @@
# typed: true # rubocop:todo Sorbet/StrictSigil # typed: strict
# frozen_string_literal: true # frozen_string_literal: true
require "context" require "context"
@ -60,7 +60,7 @@ module Utils
puts Utils.popen_read(curl, *args, url) puts Utils.popen_read(curl, *args, url)
else else
pid = spawn curl, *args, url pid = spawn curl, *args, url
Process.detach T.must(pid) Process.detach(pid)
end end
end end
@ -161,52 +161,62 @@ module Utils
report_influx(:test_bot_test, tags, fields) report_influx(:test_bot_test, tags, fields)
end end
sig { returns(T::Boolean) }
def influx_message_displayed? def influx_message_displayed?
config_true?(:influxanalyticsmessage) config_true?(:influxanalyticsmessage)
end end
sig { returns(T::Boolean) }
def messages_displayed? def messages_displayed?
config_true?(:analyticsmessage) && !!(config_true?(:analyticsmessage) &&
config_true?(:caskanalyticsmessage) && config_true?(:caskanalyticsmessage) &&
influx_message_displayed? influx_message_displayed?)
end end
sig { returns(T::Boolean) }
def disabled? def disabled?
return true if Homebrew::EnvConfig.no_analytics? return true if Homebrew::EnvConfig.no_analytics?
config_true?(:analyticsdisabled) config_true?(:analyticsdisabled)
end end
sig { returns(T::Boolean) }
def not_this_run? def not_this_run?
ENV["HOMEBREW_NO_ANALYTICS_THIS_RUN"].present? ENV["HOMEBREW_NO_ANALYTICS_THIS_RUN"].present?
end end
sig { returns(T::Boolean) }
def no_message_output? def no_message_output?
# Used by Homebrew/install # Used by Homebrew/install
ENV["HOMEBREW_NO_ANALYTICS_MESSAGE_OUTPUT"].present? ENV["HOMEBREW_NO_ANALYTICS_MESSAGE_OUTPUT"].present?
end end
sig { void }
def messages_displayed! def messages_displayed!
Homebrew::Settings.write :analyticsmessage, true Homebrew::Settings.write :analyticsmessage, true
Homebrew::Settings.write :caskanalyticsmessage, true Homebrew::Settings.write :caskanalyticsmessage, true
Homebrew::Settings.write :influxanalyticsmessage, true Homebrew::Settings.write :influxanalyticsmessage, true
end end
sig { void }
def enable! def enable!
Homebrew::Settings.write :analyticsdisabled, false Homebrew::Settings.write :analyticsdisabled, false
delete_uuid! delete_uuid!
messages_displayed! messages_displayed!
end end
sig { void }
def disable! def disable!
Homebrew::Settings.write :analyticsdisabled, true Homebrew::Settings.write :analyticsdisabled, true
delete_uuid! delete_uuid!
end end
sig { void }
def delete_uuid! def delete_uuid!
Homebrew::Settings.delete :analyticsuuid Homebrew::Settings.delete :analyticsuuid
end end
sig { params(args: Homebrew::Cmd::Info::Args, filter: T.nilable(String)).void }
def output(args:, filter: nil) def output(args:, filter: nil)
require "api" require "api"
@ -244,6 +254,7 @@ module Utils
table_output(category, days, results, os_version:, cask_install:) table_output(category, days, results, os_version:, cask_install:)
end end
sig { params(json: T::Hash[String, T.untyped], args: Homebrew::Cmd::Info::Args).void }
def output_analytics(json, args:) def output_analytics(json, args:)
full_analytics = args.analytics? || verbose? full_analytics = args.analytics? || verbose?
@ -273,6 +284,7 @@ module Utils
# It relies on screen scraping some GitHub HTML that's not available as an API. # It relies on screen scraping some GitHub HTML that's not available as an API.
# This seems very likely to break in the future. # This seems very likely to break in the future.
# That said, it's the only way to get the data we want right now. # That said, it's the only way to get the data we want right now.
sig { params(formula: Formula, args: Homebrew::Cmd::Info::Args).void }
def output_github_packages_downloads(formula, args:) def output_github_packages_downloads(formula, args:)
return unless args.github_packages_downloads? return unless args.github_packages_downloads?
return unless formula.core_formula? return unless formula.core_formula?
@ -316,6 +328,7 @@ module Utils
puts "#{number_readable(thirty_day_download_count)} (30 days)" puts "#{number_readable(thirty_day_download_count)} (30 days)"
end end
sig { params(formula: Formula, args: Homebrew::Cmd::Info::Args).void }
def formula_output(formula, args:) def formula_output(formula, args:)
return if Homebrew::EnvConfig.no_analytics? || Homebrew::EnvConfig.no_github_api? return if Homebrew::EnvConfig.no_analytics? || Homebrew::EnvConfig.no_github_api?
@ -331,6 +344,7 @@ module Utils
nil nil
end end
sig { params(cask: Cask::Cask, args: Homebrew::Cmd::Info::Args).void }
def cask_output(cask, args:) def cask_output(cask, args:)
return if Homebrew::EnvConfig.no_analytics? || Homebrew::EnvConfig.no_github_api? return if Homebrew::EnvConfig.no_analytics? || Homebrew::EnvConfig.no_github_api?
@ -388,6 +402,12 @@ module Utils
end end
end end
sig {
params(
category: String, days: String, results: T::Hash[String, Integer], os_version: T::Boolean,
cask_install: T::Boolean
).void
}
def table_output(category, days, results, os_version: false, cask_install: false) def table_output(category, days, results, os_version: false, cask_install: false)
oh1 "#{category} (#{days} days)" oh1 "#{category} (#{days} days)"
total_count = results.values.inject("+") total_count = results.values.inject("+")
@ -475,14 +495,17 @@ module Utils
"#{formatted_total_count_footer} | #{formatted_total_percent_footer}%" "#{formatted_total_count_footer} | #{formatted_total_percent_footer}%"
end end
sig { params(key: Symbol).returns(T::Boolean) }
def config_true?(key) def config_true?(key)
Homebrew::Settings.read(key) == "true" Homebrew::Settings.read(key) == "true"
end end
sig { params(count: Integer).returns(String) }
def format_count(count) def format_count(count)
count.to_s.reverse.gsub(/(\d{3})(?=\d)/, '\\1,').reverse count.to_s.reverse.gsub(/(\d{3})(?=\d)/, '\\1,').reverse
end end
sig { params(percent: T.any(Integer, Float)).returns(String) }
def format_percent(percent) def format_percent(percent)
format("%<percent>.2f", percent:) format("%<percent>.2f", percent:)
end end

View File

@ -1,10 +1,11 @@
# typed: true # rubocop:todo Sorbet/StrictSigil # typed: strict
# frozen_string_literal: true # frozen_string_literal: true
require "fcntl" require "fcntl"
require "utils/socket" require "utils/socket"
module Utils module Utils
sig { params(child_error: T::Hash[String, T.untyped]).returns(Exception) }
def self.rewrite_child_error(child_error) def self.rewrite_child_error(child_error)
inner_class = Object.const_get(child_error["json_class"]) inner_class = Object.const_get(child_error["json_class"])
error = if child_error["cmd"] && inner_class == ErrorDuringExecution error = if child_error["cmd"] && inner_class == ErrorDuringExecution
@ -33,7 +34,11 @@ module Utils
# When using this function, remember to call `exec` as soon as reasonably possible. # When using this function, remember to call `exec` as soon as reasonably possible.
# This function does not protect against the pitfalls of what you can do pre-exec in a fork. # This function does not protect against the pitfalls of what you can do pre-exec in a fork.
# See `man fork` for more information. # See `man fork` for more information.
def self.safe_fork(directory: nil, yield_parent: false) sig {
params(directory: T.nilable(String), yield_parent: T::Boolean,
_blk: T.proc.params(arg0: T.nilable(String)).void).void
}
def self.safe_fork(directory: nil, yield_parent: false, &_blk)
require "json/add/exception" require "json/add/exception"
block = proc do |tmpdir| block = proc do |tmpdir|
@ -80,8 +85,6 @@ module Utils
exit!(true) exit!(true)
end end
pid = T.must(pid)
begin begin
yield(nil) if yield_parent yield(nil) if yield_parent

View File

@ -1,4 +1,4 @@
# typed: true # rubocop:todo Sorbet/StrictSigil # typed: strict
# frozen_string_literal: true # frozen_string_literal: true
require "utils/inreplace" require "utils/inreplace"
@ -14,20 +14,20 @@ module PyPI
class Package class Package
sig { params(package_string: String, is_url: T::Boolean, python_name: String).void } sig { params(package_string: String, is_url: T::Boolean, python_name: String).void }
def initialize(package_string, is_url: false, python_name: "python") def initialize(package_string, is_url: false, python_name: "python")
@pypi_info = nil @pypi_info = T.let(nil, T.nilable(T::Array[String]))
@package_string = package_string @package_string = package_string
@is_url = is_url @is_url = is_url
@is_pypi_url = package_string.start_with? PYTHONHOSTED_URL_PREFIX @is_pypi_url = T.let(package_string.start_with?(PYTHONHOSTED_URL_PREFIX), T::Boolean)
@python_name = python_name @python_name = python_name
end end
sig { returns(String) } sig { returns(T.nilable(String)) }
def name def name
basic_metadata if @name.blank? basic_metadata if @name.blank?
@name @name
end end
sig { returns(T::Array[T.nilable(String)]) } sig { returns(T.nilable(T::Array[String])) }
def extras def extras
basic_metadata if @extras.blank? basic_metadata if @extras.blank?
@extras @extras
@ -43,7 +43,7 @@ module PyPI
def version=(new_version) def version=(new_version)
raise ArgumentError, "can't update version for non-PyPI packages" unless valid_pypi_package? raise ArgumentError, "can't update version for non-PyPI packages" unless valid_pypi_package?
@version = new_version @version = T.let(new_version, T.nilable(String))
end end
sig { returns(T::Boolean) } sig { returns(T::Boolean) }
@ -97,8 +97,8 @@ module PyPI
sig { returns(String) } sig { returns(String) }
def to_s def to_s
if valid_pypi_package? if valid_pypi_package?
out = name out = T.must(name)
out += "[#{extras.join(",")}]" if extras.present? out += "[#{extras&.join(",")}]" if extras.present?
out += "==#{version}" if version.present? out += "==#{version}" if version.present?
out out
else else
@ -132,14 +132,15 @@ module PyPI
private private
# Returns [name, [extras], version] for this package. # Returns [name, [extras], version] for this package.
sig { returns(T.nilable(T.any(String, T::Array[String]))) }
def basic_metadata def basic_metadata
if @is_pypi_url if @is_pypi_url
match = File.basename(@package_string).match(/^(.+)-([a-z\d.]+?)(?:.tar.gz|.zip)$/) match = File.basename(@package_string).match(/^(.+)-([a-z\d.]+?)(?:.tar.gz|.zip)$/)
raise ArgumentError, "Package should be a valid PyPI URL" if match.blank? raise ArgumentError, "Package should be a valid PyPI URL" if match.blank?
@name ||= PyPI.normalize_python_package match[1] @name ||= T.let(PyPI.normalize_python_package(T.must(match[1])), T.nilable(String))
@extras ||= [] @extras ||= T.let([], T.nilable(T::Array[String]))
@version ||= match[2] @version ||= T.let(match[2], T.nilable(String))
elsif @is_url elsif @is_url
ensure_formula_installed!(@python_name) ensure_formula_installed!(@python_name)
@ -162,9 +163,9 @@ module PyPI
metadata = JSON.parse(pip_output)["install"].first["metadata"] metadata = JSON.parse(pip_output)["install"].first["metadata"]
@name ||= PyPI.normalize_python_package metadata["name"] @name ||= T.let(PyPI.normalize_python_package(metadata["name"]), T.nilable(String))
@extras ||= [] @extras ||= T.let([], T.nilable(T::Array[String]))
@version ||= metadata["version"] @version ||= T.let(metadata["version"], T.nilable(String))
else else
if @package_string.include? "==" if @package_string.include? "=="
name, version = @package_string.split("==") name, version = @package_string.split("==")
@ -180,7 +181,7 @@ module PyPI
extras = [] extras = []
end end
@name ||= PyPI.normalize_python_package name @name ||= T.let(PyPI.normalize_python_package(T.must(name)), T.nilable(String))
@extras ||= extras @extras ||= extras
@version ||= version @version ||= version
end end
@ -248,7 +249,7 @@ module PyPI
missing_msg = "formulae required to update \"#{formula.name}\" resources: #{missing_dependencies.join(", ")}" missing_msg = "formulae required to update \"#{formula.name}\" resources: #{missing_dependencies.join(", ")}"
odie "Missing #{missing_msg}" unless install_dependencies odie "Missing #{missing_msg}" unless install_dependencies
ohai "Installing #{missing_msg}" ohai "Installing #{missing_msg}"
missing_dependencies.each(&method(:ensure_formula_installed!)) missing_dependencies.each(&:ensure_formula_installed!)
end end
python_deps = formula.deps python_deps = formula.deps
@ -327,12 +328,12 @@ module PyPI
# Resolve the dependency tree of all input packages # Resolve the dependency tree of all input packages
show_info = !print_only && !silent show_info = !print_only && !silent
ohai "Retrieving PyPI dependencies for \"#{input_packages.join(" ")}\"..." if show_info ohai "Retrieving PyPI dependencies for \"#{input_packages.join(" ")}\"..." if show_info
found_packages = pip_report(input_packages, python_name:, print_stderr: verbose && show_info) found_packages = pip_report(input_packages, python_name:, print_stderr: !!(verbose && show_info))
# Resolve the dependency tree of excluded packages to prune the above # Resolve the dependency tree of excluded packages to prune the above
exclude_packages.delete_if { |package| found_packages.exclude? package } exclude_packages.delete_if { |package| found_packages.exclude? package }
ohai "Retrieving PyPI dependencies for excluded \"#{exclude_packages.join(" ")}\"..." if show_info ohai "Retrieving PyPI dependencies for excluded \"#{exclude_packages.join(" ")}\"..." if show_info
exclude_packages = pip_report(exclude_packages, python_name:, print_stderr: verbose && show_info) exclude_packages = pip_report(exclude_packages, python_name:, print_stderr: !!(verbose && show_info))
exclude_packages += [Package.new(main_package.name)] unless main_package.nil? exclude_packages += [Package.new(T.must(main_package.name))] unless main_package.nil?
new_resource_blocks = "" new_resource_blocks = ""
found_packages.sort.each do |package| found_packages.sort.each do |package|
@ -404,12 +405,18 @@ module PyPI
true true
end end
sig { params(name: String).returns(String) }
def self.normalize_python_package(name) def self.normalize_python_package(name)
# This normalization is defined in the PyPA packaging specifications; # This normalization is defined in the PyPA packaging specifications;
# https://packaging.python.org/en/latest/specifications/name-normalization/#name-normalization # https://packaging.python.org/en/latest/specifications/name-normalization/#name-normalization
name.gsub(/[-_.]+/, "-").downcase name.gsub(/[-_.]+/, "-").downcase
end end
sig {
params(
packages: T::Array[Package], python_name: String, print_stderr: T::Boolean,
).returns(T::Array[Package])
}
def self.pip_report(packages, python_name: "python", print_stderr: false) def self.pip_report(packages, python_name: "python", print_stderr: false)
return [] if packages.blank? return [] if packages.blank?
@ -430,6 +437,7 @@ module PyPI
pip_report_to_packages(JSON.parse(pip_output)).uniq pip_report_to_packages(JSON.parse(pip_output)).uniq
end end
sig { params(report: T::Hash[String, T.untyped]).returns(T::Array[Package]) }
def self.pip_report_to_packages(report) def self.pip_report_to_packages(report)
return [] if report.blank? return [] if report.blank?

View File

@ -1,4 +1,4 @@
# typed: true # rubocop:todo Sorbet/StrictSigil # typed: strict
# frozen_string_literal: true # frozen_string_literal: true
module Utils module Utils
@ -98,17 +98,20 @@ module Utils
end end
end end
SHELL_PROFILE_MAP = { SHELL_PROFILE_MAP = T.let(
bash: "~/.profile", {
csh: "~/.cshrc", bash: "~/.profile",
fish: "~/.config/fish/config.fish", csh: "~/.cshrc",
ksh: "~/.kshrc", fish: "~/.config/fish/config.fish",
mksh: "~/.kshrc", ksh: "~/.kshrc",
rc: "~/.rcrc", mksh: "~/.kshrc",
sh: "~/.profile", rc: "~/.rcrc",
tcsh: "~/.tcshrc", sh: "~/.profile",
zsh: "~/.zshrc", tcsh: "~/.tcshrc",
}.freeze zsh: "~/.zshrc",
}.freeze,
T::Hash[T.nilable(Symbol), String],
)
UNSAFE_SHELL_CHAR = %r{([^A-Za-z0-9_\-.,:/@~+\n])} UNSAFE_SHELL_CHAR = %r{([^A-Za-z0-9_\-.,:/@~+\n])}

View File

@ -1,4 +1,4 @@
# typed: true # rubocop:todo Sorbet/StrictSigil # typed: strict
# frozen_string_literal: true # frozen_string_literal: true
require "utils/curl" require "utils/curl"
@ -8,7 +8,7 @@ require "utils/github"
module SPDX module SPDX
module_function module_function
DATA_PATH = (HOMEBREW_DATA_PATH/"spdx").freeze DATA_PATH = T.let((HOMEBREW_DATA_PATH/"spdx").freeze, Pathname)
API_URL = "https://api.github.com/repos/spdx/license-list-data/releases/latest" API_URL = "https://api.github.com/repos/spdx/license-list-data/releases/latest"
LICENSEREF_PREFIX = "LicenseRef-Homebrew-" LICENSEREF_PREFIX = "LicenseRef-Homebrew-"
ALLOWED_LICENSE_SYMBOLS = [ ALLOWED_LICENSE_SYMBOLS = [
@ -16,24 +16,43 @@ module SPDX
:cannot_represent, :cannot_represent,
].freeze ].freeze
sig { returns(T::Hash[String, T.untyped]) }
def license_data def license_data
@license_data ||= JSON.parse (DATA_PATH/"spdx_licenses.json").read @license_data ||= T.let(JSON.parse((DATA_PATH/"spdx_licenses.json").read), T.nilable(T::Hash[String, T.untyped]))
end end
sig { returns(T::Hash[String, T.untyped]) }
def exception_data def exception_data
@exception_data ||= JSON.parse (DATA_PATH/"spdx_exceptions.json").read @exception_data ||= T.let(JSON.parse((DATA_PATH/"spdx_exceptions.json").read),
T.nilable(T::Hash[String, T.untyped]))
end end
sig { returns(String) }
def latest_tag def latest_tag
@latest_tag ||= GitHub::API.open_rest(API_URL)["tag_name"] @latest_tag ||= T.let(GitHub::API.open_rest(API_URL)["tag_name"], T.nilable(String))
end end
sig { params(to: Pathname).void }
def download_latest_license_data!(to: DATA_PATH) def download_latest_license_data!(to: DATA_PATH)
data_url = "https://raw.githubusercontent.com/spdx/license-list-data/#{latest_tag}/json/" data_url = "https://raw.githubusercontent.com/spdx/license-list-data/#{latest_tag}/json/"
Utils::Curl.curl_download("#{data_url}licenses.json", to: to/"spdx_licenses.json") Utils::Curl.curl_download("#{data_url}licenses.json", to: to/"spdx_licenses.json")
Utils::Curl.curl_download("#{data_url}exceptions.json", to: to/"spdx_exceptions.json") Utils::Curl.curl_download("#{data_url}exceptions.json", to: to/"spdx_exceptions.json")
end end
sig {
params(
license_expression: T.any(
String,
Symbol,
T::Hash[T.any(Symbol, String), T.untyped],
T::Array[String],
),
).returns(
[
T::Array[T.any(String, Symbol)], T::Array[String]
],
)
}
def parse_license_expression(license_expression) def parse_license_expression(license_expression)
licenses = T.let([], T::Array[T.any(String, Symbol)]) licenses = T.let([], T::Array[T.any(String, Symbol)])
exceptions = T.let([], T::Array[String]) exceptions = T.let([], T::Array[String])
@ -63,6 +82,7 @@ module SPDX
[licenses, exceptions] [licenses, exceptions]
end end
sig { params(license: T.any(String, Symbol)).returns(T::Boolean) }
def valid_license?(license) def valid_license?(license)
return ALLOWED_LICENSE_SYMBOLS.include? license if license.is_a? Symbol return ALLOWED_LICENSE_SYMBOLS.include? license if license.is_a? Symbol
@ -70,22 +90,31 @@ module SPDX
license_data["licenses"].any? { |spdx_license| spdx_license["licenseId"] == license } license_data["licenses"].any? { |spdx_license| spdx_license["licenseId"] == license }
end end
sig { params(license: T.any(String, Symbol)).returns(T::Boolean) }
def deprecated_license?(license) def deprecated_license?(license)
return false if ALLOWED_LICENSE_SYMBOLS.include? license return false if ALLOWED_LICENSE_SYMBOLS.include? license
return false unless valid_license?(license) return false unless valid_license?(license)
license = license.delete_suffix "+" license = license.to_s.delete_suffix "+"
license_data["licenses"].none? do |spdx_license| license_data["licenses"].none? do |spdx_license|
spdx_license["licenseId"] == license && !spdx_license["isDeprecatedLicenseId"] spdx_license["licenseId"] == license && !spdx_license["isDeprecatedLicenseId"]
end end
end end
sig { params(exception: String).returns(T::Boolean) }
def valid_license_exception?(exception) def valid_license_exception?(exception)
exception_data["exceptions"].any? do |spdx_exception| exception_data["exceptions"].any? do |spdx_exception|
spdx_exception["licenseExceptionId"] == exception && !spdx_exception["isDeprecatedLicenseId"] spdx_exception["licenseExceptionId"] == exception && !spdx_exception["isDeprecatedLicenseId"]
end end
end end
sig {
params(
license_expression: T.any(String, Symbol, T::Hash[T.nilable(T.any(Symbol, String)), T.untyped]),
bracket: T::Boolean,
hash_type: T.nilable(T.any(String, Symbol)),
).returns(T.nilable(String))
}
def license_expression_to_string(license_expression, bracket: false, hash_type: nil) def license_expression_to_string(license_expression, bracket: false, hash_type: nil)
case license_expression case license_expression
when String when String
@ -125,6 +154,19 @@ module SPDX
end end
end end
sig {
params(
string: T.nilable(String),
).returns(
T.nilable(
T.any(
String,
Symbol,
T::Hash[T.any(String, Symbol), T.untyped],
),
),
)
}
def string_to_license_expression(string) def string_to_license_expression(string)
return if string.blank? return if string.blank?
@ -162,13 +204,23 @@ module SPDX
end end
end end
sig {
params(
license: T.any(String, Symbol),
).returns(
T.any(
[T.any(String, Symbol)],
[String, T.nilable(String), T::Boolean],
),
)
}
def license_version_info(license) def license_version_info(license)
return [license] if ALLOWED_LICENSE_SYMBOLS.include? license return [license] if ALLOWED_LICENSE_SYMBOLS.include? license
match = license.match(/-(?<version>[0-9.]+)(?:-.*?)??(?<or_later>\+|-only|-or-later)?$/) match = license.match(/-(?<version>[0-9.]+)(?:-.*?)??(?<or_later>\+|-only|-or-later)?$/)
return [license] if match.blank? return [license] if match.blank?
license_name = license.split(match[0]).first license_name = license.to_s.split(match[0].to_s).first
or_later = match["or_later"].present? && %w[+ -or-later].include?(match["or_later"]) or_later = match["or_later"].present? && %w[+ -or-later].include?(match["or_later"])
# [name, version, later versions allowed?] # [name, version, later versions allowed?]
@ -176,12 +228,18 @@ module SPDX
[license_name, match["version"], or_later] [license_name, match["version"], or_later]
end end
sig {
params(license_expression: T.any(String, Symbol, T::Hash[Symbol, T.untyped]),
forbidden_licenses: T::Hash[Symbol, T.untyped]).returns(T::Boolean)
}
def licenses_forbid_installation?(license_expression, forbidden_licenses) def licenses_forbid_installation?(license_expression, forbidden_licenses)
case license_expression case license_expression
when String, Symbol when String, Symbol
forbidden_licenses_include? license_expression.to_s, forbidden_licenses forbidden_licenses_include? license_expression.to_s, forbidden_licenses
when Hash when Hash
key = license_expression.keys.first key = license_expression.keys.first
return false if key.nil?
case key case key
when :any_of when :any_of
license_expression[key].all? { |license| licenses_forbid_installation? license, forbidden_licenses } license_expression[key].all? { |license| licenses_forbid_installation? license, forbidden_licenses }
@ -193,6 +251,12 @@ module SPDX
end end
end end
sig {
params(
license: T.any(Symbol, String),
forbidden_licenses: T::Hash[T.any(Symbol, String), T.untyped],
).returns(T::Boolean)
}
def forbidden_licenses_include?(license, forbidden_licenses) def forbidden_licenses_include?(license, forbidden_licenses)
return true if forbidden_licenses.key? license return true if forbidden_licenses.key? license

View File

@ -1,49 +1,58 @@
# typed: true # rubocop:todo Sorbet/StrictSigil # typed: strict
# frozen_string_literal: true # frozen_string_literal: true
# Various helper functions for interacting with TTYs. # Various helper functions for interacting with TTYs.
module Tty module Tty
@stream = $stdout @stream = T.let($stdout, T.nilable(T.any(IO, StringIO)))
COLOR_CODES = { COLOR_CODES = T.let(
red: 31, {
green: 32, red: 31,
yellow: 33, green: 32,
blue: 34, yellow: 33,
magenta: 35, blue: 34,
cyan: 36, magenta: 35,
default: 39, cyan: 36,
}.freeze default: 39,
}.freeze,
T::Hash[Symbol, Integer],
)
STYLE_CODES = { STYLE_CODES = T.let(
reset: 0, {
bold: 1, reset: 0,
italic: 3, bold: 1,
underline: 4, italic: 3,
strikethrough: 9, underline: 4,
no_underline: 24, strikethrough: 9,
}.freeze no_underline: 24,
}.freeze,
T::Hash[Symbol, Integer],
)
SPECIAL_CODES = { SPECIAL_CODES = T.let(
up: "1A", {
down: "1B", up: "1A",
right: "1C", down: "1B",
left: "1D", right: "1C",
erase_line: "K", left: "1D",
erase_char: "P", erase_line: "K",
}.freeze erase_char: "P",
}.freeze,
T::Hash[Symbol, String],
)
CODES = COLOR_CODES.merge(STYLE_CODES).freeze CODES = T.let(COLOR_CODES.merge(STYLE_CODES).freeze, T::Hash[Symbol, Integer])
class << self class << self
sig { params(stream: T.any(IO, StringIO), _block: T.proc.params(arg0: T.any(IO, StringIO)).void).void } sig { params(stream: T.any(IO, StringIO), _block: T.proc.params(arg0: T.any(IO, StringIO)).void).void }
def with(stream, &_block) def with(stream, &_block)
previous_stream = @stream previous_stream = @stream
@stream = stream @stream = T.let(stream, T.nilable(T.any(IO, StringIO)))
yield stream yield stream
ensure ensure
@stream = previous_stream @stream = T.let(previous_stream, T.nilable(T.any(IO, StringIO)))
end end
sig { params(string: String).returns(String) } sig { params(string: String).returns(String) }
@ -88,17 +97,17 @@ module Tty
height, width = `/bin/stty size 2>/dev/null`.presence&.split&.map(&:to_i) height, width = `/bin/stty size 2>/dev/null`.presence&.split&.map(&:to_i)
return if height.nil? || width.nil? return if height.nil? || width.nil?
@size = [height, width] @size = T.let([height, width], T.nilable([Integer, Integer]))
end end
sig { returns(Integer) } sig { returns(Integer) }
def height def height
@height ||= size&.first || `/usr/bin/tput lines 2>/dev/null`.presence&.to_i || 40 @height ||= T.let(size&.first || `/usr/bin/tput lines 2>/dev/null`.presence&.to_i || 40, T.nilable(Integer))
end end
sig { returns(Integer) } sig { returns(Integer) }
def width def width
@width ||= size&.second || `/usr/bin/tput cols 2>/dev/null`.presence&.to_i || 80 @width ||= T.let(size&.second || `/usr/bin/tput cols 2>/dev/null`.presence&.to_i || 80, T.nilable(Integer))
end end
sig { params(string: String).returns(String) } sig { params(string: String).returns(String) }
@ -115,12 +124,12 @@ module Tty
sig { void } sig { void }
def reset_escape_sequence! def reset_escape_sequence!
@escape_sequence = nil @escape_sequence = T.let(nil, T.nilable(T::Array[Integer]))
end end
CODES.each do |name, code| CODES.each do |name, code|
define_method(name) do define_method(name) do
@escape_sequence ||= [] @escape_sequence ||= T.let([], T.nilable(T::Array[Integer]))
@escape_sequence << code @escape_sequence << code
self self
end end
@ -128,7 +137,8 @@ module Tty
SPECIAL_CODES.each do |name, code| SPECIAL_CODES.each do |name, code|
define_method(name) do define_method(name) do
if @stream.tty? @stream = T.let($stdout, T.nilable(T.any(IO, StringIO)))
if @stream&.tty?
"\033[#{code}" "\033[#{code}"
else else
"" ""
@ -152,7 +162,7 @@ module Tty
return false if Homebrew::EnvConfig.no_color? return false if Homebrew::EnvConfig.no_color?
return true if Homebrew::EnvConfig.color? return true if Homebrew::EnvConfig.color?
@stream.tty? !!@stream&.tty?
end end
end end
end end