Bump some utils/ files to Sorbet typed: strict
				
					
				
			This commit is contained in:
		
							parent
							
								
									55475cb11a
								
							
						
					
					
						commit
						975a707b3c
					
				@ -1,4 +1,4 @@
 | 
			
		||||
# typed: true # rubocop:todo Sorbet/StrictSigil
 | 
			
		||||
# typed: strict
 | 
			
		||||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
require "context"
 | 
			
		||||
@ -60,7 +60,7 @@ module Utils
 | 
			
		||||
          puts Utils.popen_read(curl, *args, url)
 | 
			
		||||
        else
 | 
			
		||||
          pid = spawn curl, *args, url
 | 
			
		||||
          Process.detach T.must(pid)
 | 
			
		||||
          Process.detach(pid)
 | 
			
		||||
        end
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
@ -161,52 +161,62 @@ module Utils
 | 
			
		||||
        report_influx(:test_bot_test, tags, fields)
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      sig { returns(T::Boolean) }
 | 
			
		||||
      def influx_message_displayed?
 | 
			
		||||
        config_true?(:influxanalyticsmessage)
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      sig { returns(T::Boolean) }
 | 
			
		||||
      def messages_displayed?
 | 
			
		||||
        config_true?(:analyticsmessage) &&
 | 
			
		||||
        !!(config_true?(:analyticsmessage) &&
 | 
			
		||||
          config_true?(:caskanalyticsmessage) &&
 | 
			
		||||
          influx_message_displayed?
 | 
			
		||||
          influx_message_displayed?)
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      sig { returns(T::Boolean) }
 | 
			
		||||
      def disabled?
 | 
			
		||||
        return true if Homebrew::EnvConfig.no_analytics?
 | 
			
		||||
 | 
			
		||||
        config_true?(:analyticsdisabled)
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      sig { returns(T::Boolean) }
 | 
			
		||||
      def not_this_run?
 | 
			
		||||
        ENV["HOMEBREW_NO_ANALYTICS_THIS_RUN"].present?
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      sig { returns(T::Boolean) }
 | 
			
		||||
      def no_message_output?
 | 
			
		||||
        # Used by Homebrew/install
 | 
			
		||||
        ENV["HOMEBREW_NO_ANALYTICS_MESSAGE_OUTPUT"].present?
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      sig { void }
 | 
			
		||||
      def messages_displayed!
 | 
			
		||||
        Homebrew::Settings.write :analyticsmessage, true
 | 
			
		||||
        Homebrew::Settings.write :caskanalyticsmessage, true
 | 
			
		||||
        Homebrew::Settings.write :influxanalyticsmessage, true
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      sig { void }
 | 
			
		||||
      def enable!
 | 
			
		||||
        Homebrew::Settings.write :analyticsdisabled, false
 | 
			
		||||
        delete_uuid!
 | 
			
		||||
        messages_displayed!
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      sig { void }
 | 
			
		||||
      def disable!
 | 
			
		||||
        Homebrew::Settings.write :analyticsdisabled, true
 | 
			
		||||
        delete_uuid!
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      sig { void }
 | 
			
		||||
      def delete_uuid!
 | 
			
		||||
        Homebrew::Settings.delete :analyticsuuid
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      sig { params(args: Homebrew::Cmd::Info::Args, filter: T.nilable(String)).void }
 | 
			
		||||
      def output(args:, filter: nil)
 | 
			
		||||
        require "api"
 | 
			
		||||
 | 
			
		||||
@ -244,6 +254,7 @@ module Utils
 | 
			
		||||
        table_output(category, days, results, os_version:, cask_install:)
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      sig { params(json: T::Hash[String, T.untyped], args: Homebrew::Cmd::Info::Args).void }
 | 
			
		||||
      def output_analytics(json, args:)
 | 
			
		||||
        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.
 | 
			
		||||
      # This seems very likely to break in the future.
 | 
			
		||||
      # 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:)
 | 
			
		||||
        return unless args.github_packages_downloads?
 | 
			
		||||
        return unless formula.core_formula?
 | 
			
		||||
@ -316,6 +328,7 @@ module Utils
 | 
			
		||||
        puts "#{number_readable(thirty_day_download_count)} (30 days)"
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      sig { params(formula: Formula, args: Homebrew::Cmd::Info::Args).void }
 | 
			
		||||
      def formula_output(formula, args:)
 | 
			
		||||
        return if Homebrew::EnvConfig.no_analytics? || Homebrew::EnvConfig.no_github_api?
 | 
			
		||||
 | 
			
		||||
@ -331,6 +344,7 @@ module Utils
 | 
			
		||||
        nil
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      sig { params(cask: Cask::Cask, args: Homebrew::Cmd::Info::Args).void }
 | 
			
		||||
      def cask_output(cask, args:)
 | 
			
		||||
        return if Homebrew::EnvConfig.no_analytics? || Homebrew::EnvConfig.no_github_api?
 | 
			
		||||
 | 
			
		||||
@ -388,6 +402,12 @@ module Utils
 | 
			
		||||
        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)
 | 
			
		||||
        oh1 "#{category} (#{days} days)"
 | 
			
		||||
        total_count = results.values.inject("+")
 | 
			
		||||
@ -475,14 +495,17 @@ module Utils
 | 
			
		||||
             "#{formatted_total_count_footer} | #{formatted_total_percent_footer}%"
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      sig { params(key: Symbol).returns(T::Boolean) }
 | 
			
		||||
      def config_true?(key)
 | 
			
		||||
        Homebrew::Settings.read(key) == "true"
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      sig { params(count: Integer).returns(String) }
 | 
			
		||||
      def format_count(count)
 | 
			
		||||
        count.to_s.reverse.gsub(/(\d{3})(?=\d)/, '\\1,').reverse
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      sig { params(percent: T.any(Integer, Float)).returns(String) }
 | 
			
		||||
      def format_percent(percent)
 | 
			
		||||
        format("%<percent>.2f", percent:)
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
@ -1,10 +1,11 @@
 | 
			
		||||
# typed: true # rubocop:todo Sorbet/StrictSigil
 | 
			
		||||
# typed: strict
 | 
			
		||||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
require "fcntl"
 | 
			
		||||
require "utils/socket"
 | 
			
		||||
 | 
			
		||||
module Utils
 | 
			
		||||
  sig { params(child_error: T::Hash[String, T.untyped]).returns(Exception) }
 | 
			
		||||
  def self.rewrite_child_error(child_error)
 | 
			
		||||
    inner_class = Object.const_get(child_error["json_class"])
 | 
			
		||||
    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.
 | 
			
		||||
  # This function does not protect against the pitfalls of what you can do pre-exec in a fork.
 | 
			
		||||
  # 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"
 | 
			
		||||
 | 
			
		||||
    block = proc do |tmpdir|
 | 
			
		||||
@ -80,8 +85,6 @@ module Utils
 | 
			
		||||
          exit!(true)
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
        pid = T.must(pid)
 | 
			
		||||
 | 
			
		||||
        begin
 | 
			
		||||
          yield(nil) if yield_parent
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -1,4 +1,4 @@
 | 
			
		||||
# typed: true # rubocop:todo Sorbet/StrictSigil
 | 
			
		||||
# typed: strict
 | 
			
		||||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
require "utils/inreplace"
 | 
			
		||||
@ -14,20 +14,20 @@ module PyPI
 | 
			
		||||
  class Package
 | 
			
		||||
    sig { params(package_string: String, is_url: T::Boolean, python_name: String).void }
 | 
			
		||||
    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
 | 
			
		||||
      @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
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    sig { returns(String) }
 | 
			
		||||
    sig { returns(T.nilable(String)) }
 | 
			
		||||
    def name
 | 
			
		||||
      basic_metadata if @name.blank?
 | 
			
		||||
      @name
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    sig { returns(T::Array[T.nilable(String)]) }
 | 
			
		||||
    sig { returns(T.nilable(T::Array[String])) }
 | 
			
		||||
    def extras
 | 
			
		||||
      basic_metadata if @extras.blank?
 | 
			
		||||
      @extras
 | 
			
		||||
@ -43,7 +43,7 @@ module PyPI
 | 
			
		||||
    def version=(new_version)
 | 
			
		||||
      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
 | 
			
		||||
 | 
			
		||||
    sig { returns(T::Boolean) }
 | 
			
		||||
@ -97,8 +97,8 @@ module PyPI
 | 
			
		||||
    sig { returns(String) }
 | 
			
		||||
    def to_s
 | 
			
		||||
      if valid_pypi_package?
 | 
			
		||||
        out = name
 | 
			
		||||
        out += "[#{extras.join(",")}]" if extras.present?
 | 
			
		||||
        out = T.must(name)
 | 
			
		||||
        out += "[#{extras&.join(",")}]" if extras.present?
 | 
			
		||||
        out += "==#{version}" if version.present?
 | 
			
		||||
        out
 | 
			
		||||
      else
 | 
			
		||||
@ -132,14 +132,15 @@ module PyPI
 | 
			
		||||
    private
 | 
			
		||||
 | 
			
		||||
    # Returns [name, [extras], version] for this package.
 | 
			
		||||
    sig { returns(T.nilable(T.any(String, T::Array[String]))) }
 | 
			
		||||
    def basic_metadata
 | 
			
		||||
      if @is_pypi_url
 | 
			
		||||
        match = File.basename(@package_string).match(/^(.+)-([a-z\d.]+?)(?:.tar.gz|.zip)$/)
 | 
			
		||||
        raise ArgumentError, "Package should be a valid PyPI URL" if match.blank?
 | 
			
		||||
 | 
			
		||||
        @name ||= PyPI.normalize_python_package match[1]
 | 
			
		||||
        @extras ||= []
 | 
			
		||||
        @version ||= match[2]
 | 
			
		||||
        @name ||= T.let(PyPI.normalize_python_package(T.must(match[1])), T.nilable(String))
 | 
			
		||||
        @extras ||= T.let([], T.nilable(T::Array[String]))
 | 
			
		||||
        @version ||= T.let(match[2], T.nilable(String))
 | 
			
		||||
      elsif @is_url
 | 
			
		||||
        ensure_formula_installed!(@python_name)
 | 
			
		||||
 | 
			
		||||
@ -162,9 +163,9 @@ module PyPI
 | 
			
		||||
 | 
			
		||||
        metadata = JSON.parse(pip_output)["install"].first["metadata"]
 | 
			
		||||
 | 
			
		||||
        @name ||= PyPI.normalize_python_package metadata["name"]
 | 
			
		||||
        @extras ||= []
 | 
			
		||||
        @version ||= metadata["version"]
 | 
			
		||||
        @name ||= T.let(PyPI.normalize_python_package(metadata["name"]), T.nilable(String))
 | 
			
		||||
        @extras ||= T.let([], T.nilable(T::Array[String]))
 | 
			
		||||
        @version ||= T.let(metadata["version"], T.nilable(String))
 | 
			
		||||
      else
 | 
			
		||||
        if @package_string.include? "=="
 | 
			
		||||
          name, version = @package_string.split("==")
 | 
			
		||||
@ -180,7 +181,7 @@ module PyPI
 | 
			
		||||
          extras = []
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
        @name ||= PyPI.normalize_python_package name
 | 
			
		||||
        @name ||= T.let(PyPI.normalize_python_package(T.must(name)), T.nilable(String))
 | 
			
		||||
        @extras ||= extras
 | 
			
		||||
        @version ||= version
 | 
			
		||||
      end
 | 
			
		||||
@ -248,7 +249,7 @@ module PyPI
 | 
			
		||||
      missing_msg = "formulae required to update \"#{formula.name}\" resources: #{missing_dependencies.join(", ")}"
 | 
			
		||||
      odie "Missing #{missing_msg}" unless install_dependencies
 | 
			
		||||
      ohai "Installing #{missing_msg}"
 | 
			
		||||
      missing_dependencies.each(&method(:ensure_formula_installed!))
 | 
			
		||||
      missing_dependencies.each(&:ensure_formula_installed!)
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    python_deps = formula.deps
 | 
			
		||||
@ -327,12 +328,12 @@ module PyPI
 | 
			
		||||
    # Resolve the dependency tree of all input packages
 | 
			
		||||
    show_info = !print_only && !silent
 | 
			
		||||
    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
 | 
			
		||||
    exclude_packages.delete_if { |package| found_packages.exclude? package }
 | 
			
		||||
    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 += [Package.new(main_package.name)] unless main_package.nil?
 | 
			
		||||
    exclude_packages = pip_report(exclude_packages, python_name:, print_stderr: !!(verbose && show_info))
 | 
			
		||||
    exclude_packages += [Package.new(T.must(main_package.name))] unless main_package.nil?
 | 
			
		||||
 | 
			
		||||
    new_resource_blocks = ""
 | 
			
		||||
    found_packages.sort.each do |package|
 | 
			
		||||
@ -404,12 +405,18 @@ module PyPI
 | 
			
		||||
    true
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  sig { params(name: String).returns(String) }
 | 
			
		||||
  def self.normalize_python_package(name)
 | 
			
		||||
    # This normalization is defined in the PyPA packaging specifications;
 | 
			
		||||
    # https://packaging.python.org/en/latest/specifications/name-normalization/#name-normalization
 | 
			
		||||
    name.gsub(/[-_.]+/, "-").downcase
 | 
			
		||||
  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)
 | 
			
		||||
    return [] if packages.blank?
 | 
			
		||||
 | 
			
		||||
@ -430,6 +437,7 @@ module PyPI
 | 
			
		||||
    pip_report_to_packages(JSON.parse(pip_output)).uniq
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  sig { params(report: T::Hash[String, T.untyped]).returns(T::Array[Package]) }
 | 
			
		||||
  def self.pip_report_to_packages(report)
 | 
			
		||||
    return [] if report.blank?
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -1,4 +1,4 @@
 | 
			
		||||
# typed: true # rubocop:todo Sorbet/StrictSigil
 | 
			
		||||
# typed: strict
 | 
			
		||||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
module Utils
 | 
			
		||||
@ -98,17 +98,20 @@ module Utils
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    SHELL_PROFILE_MAP = {
 | 
			
		||||
      bash: "~/.profile",
 | 
			
		||||
      csh:  "~/.cshrc",
 | 
			
		||||
      fish: "~/.config/fish/config.fish",
 | 
			
		||||
      ksh:  "~/.kshrc",
 | 
			
		||||
      mksh: "~/.kshrc",
 | 
			
		||||
      rc:   "~/.rcrc",
 | 
			
		||||
      sh:   "~/.profile",
 | 
			
		||||
      tcsh: "~/.tcshrc",
 | 
			
		||||
      zsh:  "~/.zshrc",
 | 
			
		||||
    }.freeze
 | 
			
		||||
    SHELL_PROFILE_MAP = T.let(
 | 
			
		||||
      {
 | 
			
		||||
        bash: "~/.profile",
 | 
			
		||||
        csh:  "~/.cshrc",
 | 
			
		||||
        fish: "~/.config/fish/config.fish",
 | 
			
		||||
        ksh:  "~/.kshrc",
 | 
			
		||||
        mksh: "~/.kshrc",
 | 
			
		||||
        rc:   "~/.rcrc",
 | 
			
		||||
        sh:   "~/.profile",
 | 
			
		||||
        tcsh: "~/.tcshrc",
 | 
			
		||||
        zsh:  "~/.zshrc",
 | 
			
		||||
      }.freeze,
 | 
			
		||||
      T::Hash[T.nilable(Symbol), String],
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    UNSAFE_SHELL_CHAR = %r{([^A-Za-z0-9_\-.,:/@~+\n])}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -1,4 +1,4 @@
 | 
			
		||||
# typed: true # rubocop:todo Sorbet/StrictSigil
 | 
			
		||||
# typed: strict
 | 
			
		||||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
require "utils/curl"
 | 
			
		||||
@ -8,7 +8,7 @@ require "utils/github"
 | 
			
		||||
module SPDX
 | 
			
		||||
  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"
 | 
			
		||||
  LICENSEREF_PREFIX = "LicenseRef-Homebrew-"
 | 
			
		||||
  ALLOWED_LICENSE_SYMBOLS = [
 | 
			
		||||
@ -16,24 +16,43 @@ module SPDX
 | 
			
		||||
    :cannot_represent,
 | 
			
		||||
  ].freeze
 | 
			
		||||
 | 
			
		||||
  sig { returns(T::Hash[String, T.untyped]) }
 | 
			
		||||
  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
 | 
			
		||||
 | 
			
		||||
  sig { returns(T::Hash[String, T.untyped]) }
 | 
			
		||||
  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
 | 
			
		||||
 | 
			
		||||
  sig { returns(String) }
 | 
			
		||||
  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
 | 
			
		||||
 | 
			
		||||
  sig { params(to: Pathname).void }
 | 
			
		||||
  def download_latest_license_data!(to: DATA_PATH)
 | 
			
		||||
    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}exceptions.json", to: to/"spdx_exceptions.json")
 | 
			
		||||
  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)
 | 
			
		||||
    licenses = T.let([], T::Array[T.any(String, Symbol)])
 | 
			
		||||
    exceptions = T.let([], T::Array[String])
 | 
			
		||||
@ -63,6 +82,7 @@ module SPDX
 | 
			
		||||
    [licenses, exceptions]
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  sig { params(license: T.any(String, Symbol)).returns(T::Boolean) }
 | 
			
		||||
  def valid_license?(license)
 | 
			
		||||
    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 }
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  sig { params(license: T.any(String, Symbol)).returns(T::Boolean) }
 | 
			
		||||
  def deprecated_license?(license)
 | 
			
		||||
    return false if ALLOWED_LICENSE_SYMBOLS.include? license
 | 
			
		||||
    return false unless valid_license?(license)
 | 
			
		||||
 | 
			
		||||
    license = license.delete_suffix "+"
 | 
			
		||||
    license = license.to_s.delete_suffix "+"
 | 
			
		||||
    license_data["licenses"].none? do |spdx_license|
 | 
			
		||||
      spdx_license["licenseId"] == license && !spdx_license["isDeprecatedLicenseId"]
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  sig { params(exception: String).returns(T::Boolean) }
 | 
			
		||||
  def valid_license_exception?(exception)
 | 
			
		||||
    exception_data["exceptions"].any? do |spdx_exception|
 | 
			
		||||
      spdx_exception["licenseExceptionId"] == exception && !spdx_exception["isDeprecatedLicenseId"]
 | 
			
		||||
    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)
 | 
			
		||||
    case license_expression
 | 
			
		||||
    when String
 | 
			
		||||
@ -125,6 +154,19 @@ module SPDX
 | 
			
		||||
    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)
 | 
			
		||||
    return if string.blank?
 | 
			
		||||
 | 
			
		||||
@ -162,13 +204,23 @@ module SPDX
 | 
			
		||||
    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)
 | 
			
		||||
    return [license] if ALLOWED_LICENSE_SYMBOLS.include? license
 | 
			
		||||
 | 
			
		||||
    match = license.match(/-(?<version>[0-9.]+)(?:-.*?)??(?<or_later>\+|-only|-or-later)?$/)
 | 
			
		||||
    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"])
 | 
			
		||||
 | 
			
		||||
    # [name, version, later versions allowed?]
 | 
			
		||||
@ -176,12 +228,18 @@ module SPDX
 | 
			
		||||
    [license_name, match["version"], or_later]
 | 
			
		||||
  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)
 | 
			
		||||
    case license_expression
 | 
			
		||||
    when String, Symbol
 | 
			
		||||
      forbidden_licenses_include? license_expression.to_s, forbidden_licenses
 | 
			
		||||
    when Hash
 | 
			
		||||
      key = license_expression.keys.first
 | 
			
		||||
      return false if key.nil?
 | 
			
		||||
 | 
			
		||||
      case key
 | 
			
		||||
      when :any_of
 | 
			
		||||
        license_expression[key].all? { |license| licenses_forbid_installation? license, forbidden_licenses }
 | 
			
		||||
@ -193,6 +251,12 @@ module SPDX
 | 
			
		||||
    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)
 | 
			
		||||
    return true if forbidden_licenses.key? license
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -1,49 +1,58 @@
 | 
			
		||||
# typed: true # rubocop:todo Sorbet/StrictSigil
 | 
			
		||||
# typed: strict
 | 
			
		||||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
# Various helper functions for interacting with TTYs.
 | 
			
		||||
module Tty
 | 
			
		||||
  @stream = $stdout
 | 
			
		||||
  @stream = T.let($stdout, T.nilable(T.any(IO, StringIO)))
 | 
			
		||||
 | 
			
		||||
  COLOR_CODES = {
 | 
			
		||||
    red:     31,
 | 
			
		||||
    green:   32,
 | 
			
		||||
    yellow:  33,
 | 
			
		||||
    blue:    34,
 | 
			
		||||
    magenta: 35,
 | 
			
		||||
    cyan:    36,
 | 
			
		||||
    default: 39,
 | 
			
		||||
  }.freeze
 | 
			
		||||
  COLOR_CODES = T.let(
 | 
			
		||||
    {
 | 
			
		||||
      red:     31,
 | 
			
		||||
      green:   32,
 | 
			
		||||
      yellow:  33,
 | 
			
		||||
      blue:    34,
 | 
			
		||||
      magenta: 35,
 | 
			
		||||
      cyan:    36,
 | 
			
		||||
      default: 39,
 | 
			
		||||
    }.freeze,
 | 
			
		||||
    T::Hash[Symbol, Integer],
 | 
			
		||||
  )
 | 
			
		||||
 | 
			
		||||
  STYLE_CODES = {
 | 
			
		||||
    reset:         0,
 | 
			
		||||
    bold:          1,
 | 
			
		||||
    italic:        3,
 | 
			
		||||
    underline:     4,
 | 
			
		||||
    strikethrough: 9,
 | 
			
		||||
    no_underline:  24,
 | 
			
		||||
  }.freeze
 | 
			
		||||
  STYLE_CODES = T.let(
 | 
			
		||||
    {
 | 
			
		||||
      reset:         0,
 | 
			
		||||
      bold:          1,
 | 
			
		||||
      italic:        3,
 | 
			
		||||
      underline:     4,
 | 
			
		||||
      strikethrough: 9,
 | 
			
		||||
      no_underline:  24,
 | 
			
		||||
    }.freeze,
 | 
			
		||||
    T::Hash[Symbol, Integer],
 | 
			
		||||
  )
 | 
			
		||||
 | 
			
		||||
  SPECIAL_CODES = {
 | 
			
		||||
    up:         "1A",
 | 
			
		||||
    down:       "1B",
 | 
			
		||||
    right:      "1C",
 | 
			
		||||
    left:       "1D",
 | 
			
		||||
    erase_line: "K",
 | 
			
		||||
    erase_char: "P",
 | 
			
		||||
  }.freeze
 | 
			
		||||
  SPECIAL_CODES = T.let(
 | 
			
		||||
    {
 | 
			
		||||
      up:         "1A",
 | 
			
		||||
      down:       "1B",
 | 
			
		||||
      right:      "1C",
 | 
			
		||||
      left:       "1D",
 | 
			
		||||
      erase_line: "K",
 | 
			
		||||
      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
 | 
			
		||||
    sig { params(stream: T.any(IO, StringIO), _block: T.proc.params(arg0: T.any(IO, StringIO)).void).void }
 | 
			
		||||
    def with(stream, &_block)
 | 
			
		||||
      previous_stream = @stream
 | 
			
		||||
      @stream = stream
 | 
			
		||||
      @stream = T.let(stream, T.nilable(T.any(IO, StringIO)))
 | 
			
		||||
 | 
			
		||||
      yield stream
 | 
			
		||||
    ensure
 | 
			
		||||
      @stream = previous_stream
 | 
			
		||||
      @stream = T.let(previous_stream, T.nilable(T.any(IO, StringIO)))
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    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)
 | 
			
		||||
      return if height.nil? || width.nil?
 | 
			
		||||
 | 
			
		||||
      @size = [height, width]
 | 
			
		||||
      @size = T.let([height, width], T.nilable([Integer, Integer]))
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    sig { returns(Integer) }
 | 
			
		||||
    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
 | 
			
		||||
 | 
			
		||||
    sig { returns(Integer) }
 | 
			
		||||
    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
 | 
			
		||||
 | 
			
		||||
    sig { params(string: String).returns(String) }
 | 
			
		||||
@ -115,12 +124,12 @@ module Tty
 | 
			
		||||
 | 
			
		||||
    sig { void }
 | 
			
		||||
    def reset_escape_sequence!
 | 
			
		||||
      @escape_sequence = nil
 | 
			
		||||
      @escape_sequence = T.let(nil, T.nilable(T::Array[Integer]))
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    CODES.each do |name, code|
 | 
			
		||||
      define_method(name) do
 | 
			
		||||
        @escape_sequence ||= []
 | 
			
		||||
        @escape_sequence ||= T.let([], T.nilable(T::Array[Integer]))
 | 
			
		||||
        @escape_sequence << code
 | 
			
		||||
        self
 | 
			
		||||
      end
 | 
			
		||||
@ -128,7 +137,8 @@ module Tty
 | 
			
		||||
 | 
			
		||||
    SPECIAL_CODES.each do |name, code|
 | 
			
		||||
      define_method(name) do
 | 
			
		||||
        if @stream.tty?
 | 
			
		||||
        @stream = T.let($stdout, T.nilable(T.any(IO, StringIO)))
 | 
			
		||||
        if @stream&.tty?
 | 
			
		||||
          "\033[#{code}"
 | 
			
		||||
        else
 | 
			
		||||
          ""
 | 
			
		||||
@ -152,7 +162,7 @@ module Tty
 | 
			
		||||
      return false if Homebrew::EnvConfig.no_color?
 | 
			
		||||
      return true if Homebrew::EnvConfig.color?
 | 
			
		||||
 | 
			
		||||
      @stream.tty?
 | 
			
		||||
      !!@stream&.tty?
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user