Move o* output methods to Utils::Output

This reduces the surface area of our `Kernel` monkeypatch and removes
the need to `include Kernel` in a bunch of modules.

While we're here, also move `Kernel#require?` to `Homebrew` and fully
scope the calls to it.
This commit is contained in:
Mike McQuaid 2025-08-20 19:20:19 +01:00
parent fc85e051aa
commit a1f112f3fe
No known key found for this signature in database
116 changed files with 735 additions and 419 deletions

View File

@ -74,6 +74,7 @@ Style/Documentation:
- resource.rb
- startup/config.rb
- utils/inreplace.rb
- utils/output.rb
- utils/shebang.rb
- utils/string_inreplace_extension.rb
- version.rb

View File

@ -3,6 +3,7 @@
require "cli/parser"
require "shell_command"
require "utils/output"
module Homebrew
# Subclass this to implement a `brew` command. This is preferred to declaring a named function in the `Homebrew`
@ -19,6 +20,7 @@ module Homebrew
# @api public
class AbstractCommand
extend T::Helpers
include Utils::Output::Mixin
abstract!

View File

@ -2,10 +2,13 @@
# frozen_string_literal: true
require "fileutils"
require "utils/output"
module Homebrew
module Aliases
class Alias
include ::Utils::Output::Mixin
sig { returns(String) }
attr_accessor :name

View File

@ -2,9 +2,12 @@
# frozen_string_literal: true
require "aliases/alias"
require "utils/output"
module Homebrew
module Aliases
extend Utils::Output::Mixin
RESERVED = T.let((
Commands.internal_commands +
Commands.internal_developer_commands +

View File

@ -6,10 +6,13 @@ require "api/cask"
require "api/formula"
require "api/internal"
require "base64"
require "utils/output"
module Homebrew
# Helper functions for using Homebrew's formulae.brew.sh API.
module API
extend Utils::Output::Mixin
extend Cachable
HOMEBREW_CACHE_API = T.let((HOMEBREW_CACHE/"api").freeze, Pathname)

View File

@ -7,10 +7,12 @@ require "utils/popen"
require "utils/github/api"
require "exceptions"
require "system_command"
require "utils/output"
module Homebrew
module Attestation
extend SystemCommand::Mixin
extend Utils::Output::Mixin
# @api private
HOMEBREW_CORE_REPO = "Homebrew/homebrew-core"

View File

@ -16,6 +16,7 @@ end
std_trap = trap("INT") { exit! 130 } # no backtrace thanks
require_relative "global"
require "utils/output"
begin
trap("INT", std_trap) # restore default CTRL-C handler
@ -115,14 +116,14 @@ begin
converted_cmd = cmd.downcase.tr("-", "_")
case_error = "undefined method `#{converted_cmd}' for module Homebrew"
private_method_error = "private method `#{converted_cmd}' called for module Homebrew"
odie "Unknown command: brew #{cmd}" if [case_error, private_method_error].include?(e.message)
Utils::Output.odie "Unknown command: brew #{cmd}" if [case_error, private_method_error].include?(e.message)
raise
end
end
elsif (path = Commands.external_ruby_cmd_path(cmd))
Homebrew.running_command = cmd
require?(path)
Homebrew.require?(path)
exit Homebrew.failed? ? 1 : 0
elsif Commands.external_cmd_path(cmd)
%w[CACHE LIBRARY_PATH].each do |env|
@ -139,14 +140,16 @@ begin
possible_tap.installed? ||
(blocked_tap = Tap.untapped_official_taps.include?(possible_tap.name))
if blocked_tap
onoe <<~EOS
Utils::Output.onoe <<~EOS
`brew #{cmd}` is unavailable because #{possible_tap.name} was manually untapped.
Run `brew tap #{possible_tap.name}` to reenable `brew #{cmd}`.
EOS
end
# Check for cask explicitly because it's very common in old guides
odie "`brew cask` is no longer a `brew` command. Use `brew <command> --cask` instead." if cmd == "cask"
odie "Unknown command: brew #{cmd}"
if cmd == "cask"
Utils::Output.odie "`brew cask` is no longer a `brew` command. Use `brew <command> --cask` instead."
end
Utils::Output.odie "Unknown command: brew #{cmd}"
end
# Unset HOMEBREW_HELP to avoid confusing the tap
@ -171,7 +174,7 @@ rescue UsageError => e
require "help"
Homebrew::Help.help cmd, remaining_args: args&.remaining || [], usage_error: e.message
rescue SystemExit => e
onoe "Kernel.exit" if args&.debug? && !e.success?
Utils::Output.onoe "Kernel.exit" if args&.debug? && !e.success?
if args&.debug? || ARGV.include?("--debug")
require "utils/backtrace"
$stderr.puts Utils::Backtrace.clean(e)
@ -209,7 +212,7 @@ rescue BuildError => e
rescue RuntimeError, SystemCallError => e
raise if e.message.empty?
onoe e
Utils::Output.onoe e
if args&.debug? || ARGV.include?("--debug")
require "utils/backtrace"
$stderr.puts Utils::Backtrace.clean(e)
@ -218,7 +221,7 @@ rescue RuntimeError, SystemCallError => e
exit 1
# Catch any other types of exceptions.
rescue Exception => e # rubocop:disable Lint/RescueException
onoe e
Utils::Output.onoe e
method_deprecated_error = e.is_a?(MethodDeprecatedError)
require "utils/backtrace"

View File

@ -16,9 +16,12 @@ require "fcntl"
require "utils/socket"
require "cmd/install"
require "json/add/exception"
require "utils/output"
# A formula build.
class Build
include Utils::Output::Mixin
attr_reader :formula, :deps, :reqs, :args
def initialize(formula, options, args:)

View File

@ -6,11 +6,14 @@ require "exceptions"
require "extend/ENV"
require "utils"
require "PATH"
require "utils/output"
module Homebrew
module Bundle
module Commands
module Exec
extend Utils::Output::Mixin
PATH_LIKE_ENV_REGEX = /.+#{File::PATH_SEPARATOR}/
sig {

View File

@ -3,11 +3,14 @@
require "json"
require "tsort"
require "utils/output"
module Homebrew
module Bundle
# TODO: refactor into multiple modules
module FormulaDumper
extend Utils::Output::Mixin
def self.reset!
require "bundle/brew_services"

View File

@ -1,9 +1,13 @@
# typed: true # rubocop:todo Sorbet/StrictSigil
# frozen_string_literal: true
require "utils/output"
module Homebrew
module Bundle
module Remover
extend ::Utils::Output::Mixin
def self.remove(*args, type:, global:, file:)
require "bundle/brewfile"
require "bundle/dumper"

View File

@ -1,9 +1,13 @@
# typed: strict
# frozen_string_literal: true
require "utils/output"
module Homebrew
module Bundle
module WhalebrewDumper
extend Utils::Output::Mixin
sig { void }
def self.reset!
@images = T.let(nil, T.nilable(T::Array[String]))

View File

@ -1,9 +1,13 @@
# typed: true # rubocop:todo Sorbet/StrictSigil
# frozen_string_literal: true
require "utils/output"
module Homebrew
module Bundle
module WhalebrewInstaller
extend Utils::Output::Mixin
def self.reset!
@installed_images = nil
end

View File

@ -2,16 +2,19 @@
# frozen_string_literal: true
require "extend/object/deep_dup"
require "utils/output"
module Cask
module Artifact
# Abstract superclass for all artifacts.
class AbstractArtifact
extend T::Helpers
extend ::Utils::Output::Mixin
abstract!
include Comparable
include ::Utils::Output::Mixin
def self.english_name
@english_name ||= T.must(name).sub(/^.*:/, "").gsub(/(.)([A-Z])/, '\1 \2')

View File

@ -14,12 +14,14 @@ require "formula_name_cask_token_auditor"
require "utils/curl"
require "utils/git"
require "utils/shared_audits"
require "utils/output"
module Cask
# Audit a cask for various problems.
class Audit
include SystemCommand::Mixin
include ::Utils::Curl
include ::Utils::Output::Mixin
Error = T.type_alias do
{

View File

@ -2,10 +2,13 @@
# frozen_string_literal: true
require "cask/audit"
require "utils/output"
module Cask
# Helper class for auditing all available languages of a cask.
class Auditor
include ::Utils::Output::Mixin
# TODO: use argument forwarding (...) when Sorbet supports it in strict mode
sig {
params(

View File

@ -8,6 +8,7 @@ require "cask/dsl"
require "cask/metadata"
require "cask/tab"
require "utils/bottles"
require "utils/output"
require "api_hashable"
module Cask
@ -15,6 +16,7 @@ module Cask
class Cask
extend Forwardable
extend APIHashable
extend ::Utils::Output::Mixin
include Metadata
# The token of this {Cask}.

View File

@ -5,6 +5,7 @@ require "cask/cache"
require "cask/cask"
require "uri"
require "utils/curl"
require "utils/output"
require "extend/hash/keys"
require "api"
@ -12,12 +13,14 @@ module Cask
# Loads a cask from various sources.
module CaskLoader
extend Context
extend ::Utils::Output::Mixin
ALLOWED_URL_SCHEMES = %w[file].freeze
private_constant :ALLOWED_URL_SCHEMES
module ILoader
extend T::Helpers
include ::Utils::Output::Mixin
interface!

View File

@ -2,12 +2,15 @@
# frozen_string_literal: true
require "utils/user"
require "utils/output"
module Cask
# Helper functions for interacting with the `Caskroom` directory.
#
# @api internal
module Caskroom
extend ::Utils::Output::Mixin
sig { returns(Pathname) }
def self.path
@path ||= T.let(HOMEBREW_PREFIX/"Caskroom", T.nilable(Pathname))

View File

@ -5,6 +5,7 @@ require "autobump_constants"
require "locale"
require "lazy_object"
require "livecheck"
require "utils/output"
require "cask/artifact"
require "cask/artifact_set"
@ -32,6 +33,8 @@ require "on_system"
module Cask
# Class representing the domain-specific language used for casks.
class DSL
include ::Utils::Output::Mixin
ORDINARY_ARTIFACT_CLASSES = [
Artifact::Installer,
Artifact::App,

View File

@ -3,6 +3,7 @@
require "delegate"
require "extend/hash/keys"
require "utils/output"
module Cask
class DSL
@ -23,7 +24,7 @@ module Cask
options.assert_valid_keys(*VALID_KEYS, *ODEPRECATED_KEYS)
options.keys.intersection(ODEPRECATED_KEYS).each do |key|
Kernel.odeprecated "conflicts_with #{key}:"
::Utils::Output.odeprecated "conflicts_with #{key}:"
end
conflicts = options.transform_values { |v| Set.new(Kernel.Array(v)) }

View File

@ -3,9 +3,12 @@
require "json"
require "cmd/info"
require "utils/output"
module Cask
class Info
extend ::Utils::Output::Mixin
sig { params(cask: Cask).returns(String) }
def self.get_info(cask)
require "cask/installer"

View File

@ -5,6 +5,7 @@ require "formula_installer"
require "unpack_strategy"
require "utils/topological_hash"
require "utils/analytics"
require "utils/output"
require "cask/config"
require "cask/download"
@ -15,6 +16,9 @@ require "cask/tab"
module Cask
# Installer for a {Cask}.
class Installer
extend ::Utils::Output::Mixin
include ::Utils::Output::Mixin
sig {
params(
cask: ::Cask::Cask, command: T::Class[SystemCommand], force: T::Boolean, adopt: T::Boolean,

View File

@ -2,9 +2,12 @@
# frozen_string_literal: true
require "cask/artifact/relocated"
require "utils/output"
module Cask
class List
extend ::Utils::Output::Mixin
sig { params(casks: Cask, one: T::Boolean, full_name: T::Boolean, versions: T::Boolean).void }
def self.list_casks(*casks, one: false, full_name: false, versions: false)
output = if casks.any?

View File

@ -1,10 +1,13 @@
# typed: true # rubocop:todo Sorbet/StrictSigil
# frozen_string_literal: true
require "utils/output"
module Cask
# Helper module for reading and writing cask metadata.
module Metadata
extend T::Helpers
include ::Utils::Output::Mixin
METADATA_SUBDIR = ".metadata"
TIMESTAMP_FORMAT = "%Y%m%d%H%M%S.%L"

View File

@ -3,9 +3,12 @@
require "cask/cask_loader"
require "utils/inreplace"
require "utils/output"
module Cask
class Migrator
include ::Utils::Output::Mixin
sig { returns(Cask) }
attr_reader :old_cask, :new_cask

View File

@ -2,10 +2,13 @@
# frozen_string_literal: true
require "cask/macos"
require "utils/output"
module Cask
# Helper class for uninstalling `.pkg` installers.
class Pkg
include ::Utils::Output::Mixin
sig { params(regexp: String, command: T.class_of(SystemCommand)).returns(T::Array[Pkg]) }
def self.all_matching(regexp, command)
command.run("/usr/sbin/pkgutil", args: ["--pkgs=#{regexp}"]).stdout.split("\n").map do |package_id|

View File

@ -4,11 +4,13 @@
require "development_tools"
require "cask/exceptions"
require "system_command"
require "utils/output"
module Cask
# Helper module for quarantining files.
module Quarantine
extend SystemCommand::Mixin
extend ::Utils::Output::Mixin
QUARANTINE_ATTRIBUTE = "com.apple.quarantine"

View File

@ -1,8 +1,12 @@
# typed: strict
# frozen_string_literal: true
require "utils/output"
module Cask
class Reinstall
extend ::Utils::Output::Mixin
sig {
params(
casks: ::Cask::Cask, verbose: T::Boolean, force: T::Boolean, skip_cask_deps: T::Boolean, binaries: T::Boolean,

View File

@ -2,10 +2,12 @@
# frozen_string_literal: true
require "utils/user"
require "utils/output"
module Cask
# Helper functions for staged casks.
module Staged
include ::Utils::Output::Mixin
extend T::Helpers
requires_ancestor { ::Cask::DSL::Base }

View File

@ -1,8 +1,12 @@
# typed: strict
# frozen_string_literal: true
require "utils/output"
module Cask
class Uninstall
extend ::Utils::Output::Mixin
sig { params(casks: ::Cask::Cask, binaries: T::Boolean, force: T::Boolean, verbose: T::Boolean).void }
def self.uninstall_casks(*casks, binaries: false, force: false, verbose: false)
require "cask/installer"

View File

@ -3,9 +3,12 @@
require "env_config"
require "cask/config"
require "utils/output"
module Cask
class Upgrade
extend ::Utils::Output::Mixin
sig {
params(
casks: Cask,

View File

@ -3,10 +3,13 @@
require "utils/user"
require "open3"
require "utils/output"
module Cask
# Helper functions for various cask operations.
module Utils
extend ::Utils::Output::Mixin
BUG_REPORTS_URL = "https://github.com/Homebrew/homebrew-cask#reporting-bugs"
def self.gain_permissions_mkpath(path, command: SystemCommand)

View File

@ -1,6 +1,8 @@
# typed: strict
# frozen_string_literal: true
require "utils/output"
# Cleans a newly installed keg.
# By default:
#
@ -13,6 +15,7 @@
# * removes unresolved symlinks
class Cleaner
include Context
include Utils::Output::Mixin
# Create a cleaner for the given formula.
sig { params(formula: Formula).void }

View File

@ -2,6 +2,7 @@
# frozen_string_literal: true
require "utils/bottles"
require "utils/output"
require "formula"
require "cask/cask_loader"
@ -9,6 +10,9 @@ require "cask/cask_loader"
module Homebrew
# Helper class for cleaning up the Homebrew cache.
class Cleanup
extend Utils::Output::Mixin
include Utils::Output::Mixin
CLEANUP_DEFAULT_DAYS = Homebrew::EnvConfig.cleanup_periodic_full_days.to_i.freeze
GH_ACTIONS_ARTIFACT_CLEANUP_DAYS = 3
private_constant :CLEANUP_DEFAULT_DAYS, :GH_ACTIONS_ARTIFACT_CLEANUP_DAYS

View File

@ -2,11 +2,13 @@
# frozen_string_literal: true
require "cli/args"
require "utils/output"
module Homebrew
module CLI
# Helper class for loading formulae/casks from named arguments.
class NamedArgs < Array
include Utils::Output::Mixin
extend T::Generic
Elem = type_member(:out) { { fixed: String } }

View File

@ -10,10 +10,13 @@ require "commands"
require "optparse"
require "utils/tty"
require "utils/formatter"
require "utils/output"
module Homebrew
module CLI
class Parser
include Utils::Output::Mixin
ArgType = T.type_alias { T.any(NilClass, Symbol, T::Array[String], T::Array[Symbol]) }
HIDDEN_DESC_PLACEHOLDER = "@@HIDDEN@@"
SYMBOL_TO_USAGE_MAPPING = T.let({
@ -40,7 +43,7 @@ module Homebrew
cmd_name = cmd_args_method_name.to_s.delete_suffix("_args").tr("_", "-")
begin
if require?(cmd_path)
if Homebrew.require?(cmd_path)
cmd = Homebrew::AbstractCommand.command(cmd_name)
if cmd
cmd.parser

View File

@ -425,6 +425,8 @@ end
require "extend/os/cmd/update-report"
class Reporter
include Utils::Output::Mixin
class ReporterRevisionUnsetError < RuntimeError
sig { params(var_name: String).void }
def initialize(var_name)
@ -782,6 +784,8 @@ class Reporter
end
class ReporterHub
include Utils::Output::Mixin
sig { returns(T::Array[Reporter]) }
attr_reader :reporters

View File

@ -1,6 +1,8 @@
# typed: true # rubocop:todo Sorbet/StrictSigil
# frozen_string_literal: true
require "utils"
# Helper functions for commands.
module Commands
HOMEBREW_CMD_PATH = (HOMEBREW_LIBRARY_PATH/"cmd").freeze
@ -34,11 +36,11 @@ module Commands
DESCRIPTION_SPLITTING_PATTERN = /\.(?>\s|$)/
def self.valid_internal_cmd?(cmd)
require?(HOMEBREW_CMD_PATH/cmd)
Homebrew.require?(HOMEBREW_CMD_PATH/cmd)
end
def self.valid_internal_dev_cmd?(cmd)
require?(HOMEBREW_DEV_CMD_PATH/cmd)
Homebrew.require?(HOMEBREW_DEV_CMD_PATH/cmd)
end
def self.valid_ruby_cmd?(cmd)
@ -76,7 +78,7 @@ module Commands
# Ruby commands which can be `require`d without being run.
def self.external_ruby_v2_cmd_path(cmd)
path = which("#{cmd}.rb", tap_cmd_directories)
path if require?(path)
path if Homebrew.require?(path)
end
# Ruby commands which are run by being `require`d.

View File

@ -8,6 +8,8 @@ require "erb"
module Homebrew
# Helper functions for generating shell completions.
module Completions
extend Utils::Output::Mixin
Variables = Struct.new(
:aliases,
:builtin_command_descriptions,

View File

@ -8,6 +8,7 @@ require "formulary"
require "version"
require "development_tools"
require "utils/shell"
require "utils/output"
require "system_config"
require "cask/caskroom"
require "cask/quarantine"
@ -16,6 +17,8 @@ require "system_command"
module Homebrew
# Module containing diagnostic checks.
module Diagnostic
extend Utils::Output::Mixin
sig {
params(formulae: T::Array[Formula], hide: T::Array[String], _block: T.nilable(
T.proc.params(formula_name: String, missing_dependencies: T::Array[Formula]).void,

View File

@ -6,9 +6,12 @@ require "concurrent/promises"
require "concurrent/executors"
require "retryable_download"
require "resource"
require "utils/output"
module Homebrew
class DownloadQueue
include Utils::Output::Mixin
sig { params(retries: Integer, force: T::Boolean, pour: T::Boolean).void }
def initialize(retries: 1, force: false, pour: false)
@concurrency = T.let(EnvConfig.download_concurrency, Integer)

View File

@ -7,6 +7,7 @@ require "unpack_strategy"
require "lazy_object"
require "lock_file"
require "system_command"
require "utils/output"
# Need to define this before requiring Mechanize to avoid:
# uninitialized constant Mechanize
@ -27,6 +28,7 @@ class AbstractDownloadStrategy
include FileUtils
include Context
include SystemCommand::Mixin
include Utils::Output::Mixin
abstract!

View File

@ -4,9 +4,11 @@
require "url"
require "checksum"
require "download_strategy"
require "utils/output"
module Downloadable
include Context
include Utils::Output::Mixin
extend T::Helpers
abstract!

View File

@ -1,11 +1,16 @@
# typed: strict
# frozen_string_literal: true
require "utils/output"
module Homebrew
# Helper module for querying Homebrew-specific environment variables.
#
# @api internal
module EnvConfig
include Utils::Output::Mixin
extend Utils::Output::Mixin
module_function
ENVS = T.let({

View File

@ -1,6 +1,8 @@
# typed: true # rubocop:todo Sorbet/StrictSigil
# frozen_string_literal: true
require "utils/output"
# Raised when a command is used wrong.
#
# @api internal
@ -463,6 +465,8 @@ end
# Raised when an error occurs during a formula build.
class BuildError < RuntimeError
include Utils::Output::Mixin
attr_reader :cmd, :args, :env
attr_accessor :formula, :options

View File

@ -3,6 +3,7 @@
require "extend/ENV/shared"
require "development_tools"
require "utils/output"
# ### Why `superenv`?
#
@ -16,6 +17,7 @@ require "development_tools"
# 8. Build-system agnostic configuration of the toolchain
module Superenv
include SharedEnvExtension
include Utils::Output::Mixin
attr_accessor :keg_only_deps, :deps, :run_time_deps

View File

@ -1,9 +1,7 @@
# typed: true # rubocop:todo Sorbet/StrictSigil
# frozen_string_literal: true
# Contains shorthand Homebrew utility methods like `ohai`, `opoo`, `odisabled`.
# TODO: move these out of `Kernel` into `Homebrew::GlobalMethods` and add
# necessary Sorbet and global Kernel inclusions.
require "utils/output"
module Kernel
sig { params(env: T.nilable(String)).returns(T::Boolean) }
@ -14,279 +12,6 @@ module Kernel
end
private :superenv?
sig { params(path: T.nilable(T.any(String, Pathname))).returns(T::Boolean) }
def require?(path)
return false if path.nil?
if defined?(Warnings)
# Work around require warning when done repeatedly:
# https://bugs.ruby-lang.org/issues/21091
Warnings.ignore(/already initialized constant/, /previous definition of/) do
require path.to_s
end
else
require path.to_s
end
true
rescue LoadError
false
end
sig { params(title: String).returns(String) }
def ohai_title(title)
verbose = if respond_to?(:verbose?)
T.unsafe(self).verbose?
else
Context.current.verbose?
end
title = Tty.truncate(title.to_s) if $stdout.tty? && !verbose
Formatter.headline(title, color: :blue)
end
sig { params(title: T.any(String, Exception), sput: T.anything).void }
def ohai(title, *sput)
puts ohai_title(title.to_s)
puts sput
end
sig { params(title: T.any(String, Exception), sput: T.anything, always_display: T::Boolean).void }
def odebug(title, *sput, always_display: false)
debug = if respond_to?(:debug)
T.unsafe(self).debug?
else
Context.current.debug?
end
return if !debug && !always_display
$stderr.puts Formatter.headline(title.to_s, color: :magenta)
$stderr.puts sput unless sput.empty?
end
sig { params(title: String, truncate: T.any(Symbol, T::Boolean)).returns(String) }
def oh1_title(title, truncate: :auto)
verbose = if respond_to?(:verbose?)
T.unsafe(self).verbose?
else
Context.current.verbose?
end
title = Tty.truncate(title.to_s) if $stdout.tty? && !verbose && truncate == :auto
Formatter.headline(title, color: :green)
end
sig { params(title: String, truncate: T.any(Symbol, T::Boolean)).void }
def oh1(title, truncate: :auto)
puts oh1_title(title, truncate:)
end
# Print a warning message.
#
# @api public
sig { params(message: T.any(String, Exception)).void }
def opoo(message)
require "utils/github/actions"
return if GitHub::Actions.puts_annotation_if_env_set!(:warning, message.to_s)
require "utils/formatter"
Tty.with($stderr) do |stderr|
stderr.puts Formatter.warning(message, label: "Warning")
end
end
# Print a warning message only if not running in GitHub Actions.
#
# @api public
sig { params(message: T.any(String, Exception)).void }
def opoo_outside_github_actions(message)
require "utils/github/actions"
return if GitHub::Actions.env_set?
opoo(message)
end
# Print an error message.
#
# @api public
sig { params(message: T.any(String, Exception)).void }
def onoe(message)
require "utils/github/actions"
return if GitHub::Actions.puts_annotation_if_env_set!(:error, message.to_s)
require "utils/formatter"
Tty.with($stderr) do |stderr|
stderr.puts Formatter.error(message, label: "Error")
end
end
# Print an error message and fail at the end of the program.
#
# @api public
sig { params(error: T.any(String, Exception)).void }
def ofail(error)
onoe error
Homebrew.failed = true
end
# Print an error message and fail immediately.
#
# @api public
sig { params(error: T.any(String, Exception)).returns(T.noreturn) }
def odie(error)
onoe error
exit 1
end
# Output a deprecation warning/error message.
sig {
params(method: String, replacement: T.nilable(T.any(String, Symbol)), disable: T::Boolean,
disable_on: T.nilable(Time), disable_for_developers: T::Boolean, caller: T::Array[String]).void
}
def odeprecated(method, replacement = nil,
disable: false,
disable_on: nil,
disable_for_developers: true,
caller: send(:caller))
replacement_message = if replacement
"Use #{replacement} instead."
else
"There is no replacement."
end
unless disable_on.nil?
if disable_on > Time.now
will_be_disabled_message = " and will be disabled on #{disable_on.strftime("%Y-%m-%d")}"
else
disable = true
end
end
verb = if disable
"disabled"
else
"deprecated#{will_be_disabled_message}"
end
# Try to show the most relevant location in message, i.e. (if applicable):
# - Location in a formula.
# - Location of caller of deprecated method (if all else fails).
backtrace = caller
# Don't throw deprecations at all for cached, .brew or .metadata files.
return if backtrace.any? do |line|
next true if line.include?(HOMEBREW_CACHE.to_s)
next true if line.include?("/.brew/")
next true if line.include?("/.metadata/")
next false unless line.match?(HOMEBREW_TAP_PATH_REGEX)
path = Pathname(line.split(":", 2).first)
next false unless path.file?
next false unless path.readable?
formula_contents = path.read
formula_contents.include?(" deprecate! ") || formula_contents.include?(" disable! ")
end
tap_message = T.let(nil, T.nilable(String))
backtrace.each do |line|
next unless (match = line.match(HOMEBREW_TAP_PATH_REGEX))
require "tap"
tap = Tap.fetch(match[:user], match[:repository])
tap_message = "\nPlease report this issue to the #{tap.full_name} tap"
tap_message += " (not Homebrew/* repositories)" unless tap.official?
tap_message += ", or even better, submit a PR to fix it" if replacement
tap_message << ":\n #{line.sub(/^(.*:\d+):.*$/, '\1')}\n\n"
break
end
file, line, = backtrace.first.split(":")
line = line.to_i if line.present?
message = "Calling #{method} is #{verb}! #{replacement_message}"
message << tap_message if tap_message
message.freeze
disable = true if disable_for_developers && Homebrew::EnvConfig.developer?
if disable || Homebrew.raise_deprecation_exceptions?
require "utils/github/actions"
GitHub::Actions.puts_annotation_if_env_set!(:error, message, file:, line:)
exception = MethodDeprecatedError.new(message)
exception.set_backtrace(backtrace)
raise exception
elsif !Homebrew.auditing?
opoo message
end
end
sig {
params(method: String, replacement: T.nilable(T.any(String, Symbol)),
disable_on: T.nilable(Time), disable_for_developers: T::Boolean, caller: T::Array[String]).void
}
def odisabled(method, replacement = nil,
disable_on: nil,
disable_for_developers: true,
caller: send(:caller))
# This odeprecated should stick around indefinitely.
odeprecated(method, replacement, disable: true, disable_on:, disable_for_developers:, caller:)
end
sig { params(string: String).returns(String) }
def pretty_installed(string)
if !$stdout.tty?
string
elsif Homebrew::EnvConfig.no_emoji?
Formatter.success("#{Tty.bold}#{string} (installed)#{Tty.reset}")
else
"#{Tty.bold}#{string} #{Formatter.success("")}#{Tty.reset}"
end
end
sig { params(string: String).returns(String) }
def pretty_outdated(string)
if !$stdout.tty?
string
elsif Homebrew::EnvConfig.no_emoji?
Formatter.error("#{Tty.bold}#{string} (outdated)#{Tty.reset}")
else
"#{Tty.bold}#{string} #{Formatter.warning("")}#{Tty.reset}"
end
end
sig { params(string: String).returns(String) }
def pretty_uninstalled(string)
if !$stdout.tty?
string
elsif Homebrew::EnvConfig.no_emoji?
Formatter.error("#{Tty.bold}#{string} (uninstalled)#{Tty.reset}")
else
"#{Tty.bold}#{string} #{Formatter.error("")}#{Tty.reset}"
end
end
sig { params(seconds: T.nilable(T.any(Integer, Float))).returns(String) }
def pretty_duration(seconds)
seconds = seconds.to_i
res = +""
if seconds > 59
minutes = seconds / 60
seconds %= 60
res = +Utils.pluralize("minute", minutes, include_count: true)
return res.freeze if seconds.zero?
res << " "
end
res << Utils.pluralize("second", seconds, include_count: true)
res.freeze
end
sig { params(formula: T.nilable(Formula)).void }
def interactive_shell(formula = nil)
unless formula.nil?
@ -368,7 +93,7 @@ module Kernel
editor ||= "vim"
unless silent
opoo <<~EOS
Utils::Output.opoo <<~EOS
Using #{editor} because no editor was set in the environment.
This may change in the future, so we recommend setting `$EDITOR`
or `$HOMEBREW_EDITOR` to your preferred text editor.

View File

@ -1,6 +1,8 @@
# typed: strict
# frozen_string_literal: true
require "utils/output"
module OS
module Linux
module Install
@ -108,7 +110,7 @@ module OS
::Kernel.system HOMEBREW_PREFIX/"opt/glibc/sbin/ldconfig"
end
else
::Kernel.odie "#{HOMEBREW_PREFIX}/lib does not exist!" unless (HOMEBREW_PREFIX/"lib").readable?
Utils::Output.odie "#{HOMEBREW_PREFIX}/lib does not exist!" unless (HOMEBREW_PREFIX/"lib").readable?
end
GCC_RUNTIME_LIBS.each do |library|

View File

@ -1,11 +1,14 @@
# typed: strict
# frozen_string_literal: true
require "utils/output"
module OS
module Mac
module Readall
module ClassMethods
extend T::Helpers
include ::Utils::Output::Mixin
requires_ancestor { Kernel }

View File

@ -4,12 +4,14 @@
require "system_command"
require "extend/pathname/disk_usage_extension"
require "extend/pathname/observer_pathname_extension"
require "utils/output"
# Homebrew extends Ruby's `Pathname` to make our code more readable.
# @see https://ruby-doc.org/stdlib-2.6.3/libdoc/pathname/rdoc/Pathname.html Ruby's Pathname API
class Pathname
include SystemCommand::Mixin
include DiskUsageExtension
include Utils::Output::Mixin
# Moves a file from the original location to the {Pathname}'s.
#

View File

@ -2,8 +2,11 @@
# frozen_string_literal: true
require "time"
require "utils/output"
class Time
include Utils::Output::Mixin
# Backwards compatibility for formulae that used this ActiveSupport extension
sig { returns(String) }
def rfc3339

View File

@ -41,6 +41,7 @@ require "utils/spdx"
require "on_system"
require "api"
require "api_hashable"
require "utils/output"
# A formula provides instructions and metadata for Homebrew to install a piece
# of software. Every Homebrew formula is a {Formula}.
@ -75,6 +76,7 @@ class Formula
include FileUtils
include Utils::Shebang
include Utils::Shell
include Utils::Output::Mixin
include Context
include OnSystem::MacOSAndLinux
include Homebrew::Livecheck::Constants
@ -82,6 +84,7 @@ class Formula
extend Cachable
extend APIHashable
extend T::Helpers
extend Utils::Output::Mixin
abstract!

View File

@ -1,10 +1,13 @@
# typed: strict
# frozen_string_literal: true
require "utils/output"
module Homebrew
# Helper functions available in formula `test` blocks.
module Assertions
include Context
include ::Utils::Output::Mixin
extend T::Helpers
requires_ancestor { Kernel }

View File

@ -6,12 +6,14 @@ require "formula_versions"
require "formula_name_cask_token_auditor"
require "resource_auditor"
require "utils/shared_audits"
require "utils/output"
module Homebrew
# Auditor for checking common violations in {Formula}e.
class FormulaAuditor
include FormulaCellarChecks
include Utils::Curl
include Utils::Output::Mixin
attr_reader :formula, :text, :problems, :new_formula_problems

View File

@ -4,10 +4,13 @@
require "digest"
require "erb"
require "utils/github"
require "utils/output"
module Homebrew
# Class for generating a formula from a template.
class FormulaCreator
include Utils::Output::Mixin
sig { returns(String) }
attr_accessor :name

View File

@ -24,10 +24,12 @@ require "service"
require "attestation"
require "sbom"
require "utils/fork"
require "utils/output"
# Installer for a formula.
class FormulaInstaller
include FormulaCellarChecks
include Utils::Output::Mixin
ETC_VAR_DIRS = T.let([HOMEBREW_PREFIX/"etc", HOMEBREW_PREFIX/"var"].freeze, T::Array[Pathname])

View File

@ -2,12 +2,14 @@
# frozen_string_literal: true
require "formula"
require "utils/output"
# Helper class for traversing a formula's previous versions.
#
# @api internal
class FormulaVersions
include Context
include Utils::Output::Mixin
IGNORED_EXCEPTIONS = [
ArgumentError, NameError, SyntaxError, TypeError,

View File

@ -6,6 +6,7 @@ require "cachable"
require "tab"
require "utils"
require "utils/bottles"
require "utils/output"
require "service"
require "utils/curl"
require "deprecate_disable"
@ -18,6 +19,8 @@ require "tap"
module Formulary
extend Context
extend Cachable
extend Utils::Output::Mixin
include Utils::Output::Mixin
ALLOWED_URL_SCHEMES = %w[file].freeze
private_constant :ALLOWED_URL_SCHEMES
@ -559,6 +562,7 @@ module Formulary
# Subclasses implement loaders for particular sources of formulae.
class FormulaLoader
include Context
include Utils::Output::Mixin
# The formula's name.
sig { returns(String) }
@ -609,6 +613,8 @@ module Formulary
# Loads a formula from a bottle.
class FromBottleLoader < FormulaLoader
include Utils::Output::Mixin
sig {
params(ref: T.any(String, Pathname, URI::Generic), from: T.nilable(Symbol), warn: T::Boolean)
.returns(T.nilable(T.attached_class))

View File

@ -1,5 +0,0 @@
# typed: strict
module Formulary
include Kernel
end

View File

@ -3,6 +3,7 @@
require "utils/curl"
require "utils/gzip"
require "utils/output"
require "json"
require "zlib"
require "extend/hash/keys"
@ -12,6 +13,7 @@ require "system_command"
class GitHubPackages
include Context
include SystemCommand::Mixin
include Utils::Output::Mixin
URL_DOMAIN = "ghcr.io"
URL_PREFIX = "https://#{URL_DOMAIN}/v2/".freeze

View File

@ -2,11 +2,13 @@
# frozen_string_literal: true
require "utils/github"
require "utils/output"
require "json"
# GitHub Releases client.
class GitHubReleases
include Context
include Utils::Output::Mixin
URL_REGEX = %r{https://github\.com/([\w-]+)/([\w-]+)?/releases/download/(.+)}

View File

@ -3,10 +3,13 @@
require "cli/parser"
require "commands"
require "utils/output"
module Homebrew
# Helper module for printing help output.
module Help
extend Utils::Output::Mixin
sig {
params(
cmd: T.nilable(String),

View File

@ -7,10 +7,13 @@ require "hardware"
require "development_tools"
require "upgrade"
require "download_queue"
require "utils/output"
module Homebrew
# Helper module for performing (pre-)install checks.
module Install
extend Utils::Output::Mixin
class << self
sig { params(all_fatal: T::Boolean).void }
def perform_preinstall_checks_once(all_fatal: false)

View File

@ -5,10 +5,12 @@ require "keg_relocate"
require "language/python"
require "lock_file"
require "cachable"
require "utils/output"
# Installation prefix of a formula.
class Keg
extend Cachable
include Utils::Output::Mixin
# Error for when a keg is already linked.
class AlreadyLinkedError < RuntimeError

View File

@ -1,11 +1,15 @@
# typed: strict
# frozen_string_literal: true
require "utils/output"
module Language
# Helper functions for Node formulae.
#
# @api public
module Node
extend ::Utils::Output::Mixin
sig { returns(String) }
def self.npm_cache_config
"cache=#{HOMEBREW_CACHE}/npm_cache"

View File

@ -5,9 +5,12 @@ require "keg"
require "formula"
require "linkage_cache_store"
require "fiddle"
require "utils/output"
# Check for broken/missing linkage in a formula's keg.
class LinkageChecker
include Utils::Output::Mixin
attr_reader :undeclared_deps, :keg, :formula, :store
def initialize(keg, formula = nil, cache_db:, rebuild_cache: false)

View File

@ -7,12 +7,15 @@ require "livecheck/livecheck_version"
require "livecheck/skip_conditions"
require "livecheck/strategy"
require "addressable"
require "utils/output"
module Homebrew
# The {Livecheck} module consists of methods used by the `brew livecheck`
# command. These methods print the requested livecheck information
# for formulae.
module Livecheck
extend Utils::Output::Mixin
NO_CURRENT_VERSION_MSG = "Unable to identify current version"
NO_VERSIONS_MSG = "Unable to get versions"

View File

@ -2,9 +2,12 @@
# frozen_string_literal: true
require "fcntl"
require "utils/output"
# A lock file to prevent multiple Homebrew processes from modifying the same path.
class LockFile
include Utils::Output::Mixin
class OpenFileChangedOnDisk < RuntimeError; end
private_constant :OpenFileChangedOnDisk

View File

@ -1,9 +1,13 @@
# typed: strict
# frozen_string_literal: true
require "utils/output"
# A {Messages} object collects messages that may need to be displayed together
# at the end of a multi-step `brew` command run.
class Messages
include ::Utils::Output::Mixin
sig { returns(T::Array[{ package: String, caveats: T.any(String, Caveats) }]) }
attr_reader :caveats

View File

@ -4,10 +4,13 @@
require "lock_file"
require "keg"
require "tab"
require "utils/output"
# Helper class for migrating a formula from an old to a new name.
class Migrator
extend Utils::Output::Mixin
include Context
include Utils::Output::Mixin
# Error for when a migration is necessary.
class MigrationNeededError < RuntimeError

View File

@ -2,10 +2,13 @@
# frozen_string_literal: true
require "formulary"
require "utils/output"
module Homebrew
# Helper module for checking if there is a reason a formula is missing.
module MissingFormula
extend Utils::Output::Mixin
class << self
sig { params(name: String, silent: T::Boolean, show_info: T::Boolean).returns(T.nilable(String)) }
def reason(name, silent: false, show_info: false)

View File

@ -1,10 +1,14 @@
# typed: strict
# frozen_string_literal: true
require "utils/output"
# Performs {Formula#mktemp}'s functionality and tracks the results.
# Each instance is only intended to be used once.
# Can also be used to create a temporary directory with the brew instance's group.
class Mktemp
include Utils::Output::Mixin
# Path to the tmpdir used in this run
sig { returns(T.nilable(Pathname)) }
attr_reader :tmpdir

View File

@ -3,6 +3,7 @@
require "resource"
require "erb"
require "utils/output"
# Helper module for creating patches.
module Patch
@ -96,6 +97,8 @@ end
# A file containing a patch.
class ExternalPatch
include Utils::Output::Mixin
extend Forwardable
attr_reader :resource, :strip

View File

@ -4,11 +4,13 @@
require "formula"
require "cask/cask_loader"
require "system_command"
require "utils/output"
# Helper module for validating syntax in taps.
module Readall
extend Cachable
extend SystemCommand::Mixin
extend Utils::Output::Mixin
# TODO: remove this once the `MacOS` module is undefined on Linux
MACOS_MODULE_REGEX = /\b(MacOS|OS::Mac)(\.|::)\b/

View File

@ -3,6 +3,7 @@
require "development_tools"
require "messages"
require "utils/output"
# Needed to handle circular require dependency.
# rubocop:disable Lint/EmptyClass
@ -11,6 +12,8 @@ class FormulaInstaller; end
module Homebrew
module Reinstall
extend Utils::Output::Mixin
class InstallationContext < T::Struct
const :formula_installer, ::FormulaInstaller
const :keg, T.nilable(Keg)

View File

@ -5,12 +5,14 @@ require "dependable"
require "dependency"
require "dependencies"
require "build_environment"
require "utils/output"
# A base class for non-formula requirements needed by formulae.
# A fatal requirement is one that will fail the build if it is not present.
# By default, requirements are non-fatal.
class Requirement
include Dependable
include Utils::Output::Mixin
extend Cachable
extend T::Helpers

View File

@ -5,6 +5,7 @@ require "downloadable"
require "mktemp"
require "livecheck"
require "on_system"
require "utils/output"
# Resource is the fundamental representation of an external resource. The
# primary formula download, along with other declared resources, are instances
@ -13,6 +14,7 @@ class Resource
include Downloadable
include FileUtils
include OnSystem::MacOSAndLinux
include Utils::Output::Mixin
attr_reader :source_modified_time, :patches, :owner
attr_writer :checksum

View File

@ -3,10 +3,12 @@
require "bottle"
require "api/json_download"
require "utils/output"
module Homebrew
class RetryableDownload
include Downloadable
include Utils::Output::Mixin
sig { override.returns(T.any(NilClass, String, URL)) }
def url = downloadable.url

View File

@ -6,9 +6,12 @@ require "io/console"
require "pty"
require "tempfile"
require "utils/fork"
require "utils/output"
# Helper class for running a sub-process inside of a sandboxed environment.
class Sandbox
include Utils::Output::Mixin
SANDBOX_EXEC = "/usr/bin/sandbox-exec"
# This is defined in the macOS SDK but Ruby unfortunately does not expose it.

View File

@ -6,9 +6,12 @@ require "json"
require "development_tools"
require "cachable"
require "utils/curl"
require "utils/output"
# Rather than calling `new` directly, use one of the class methods like {SBOM.create}.
class SBOM
include Utils::Output::Mixin
FILENAME = "sbom.spdx.json"
SCHEMA_FILE = (HOMEBREW_LIBRARY_PATH/"data/schemas/sbom.json").freeze
@ -87,7 +90,7 @@ class SBOM
sig { params(bottling: T::Boolean).returns(T::Array[T::Hash[String, T.untyped]]) }
def schema_validation_errors(bottling: false)
unless require? "json_schemer"
unless Homebrew.require? "json_schemer"
error_message = "Need json_schemer to validate SBOM, run `brew install-bundler-gems --add-groups=bottle`!"
odie error_message if ENV["HOMEBREW_ENFORCE_SBOM"]
return []

View File

@ -2,10 +2,13 @@
# frozen_string_literal: true
require "description_cache_store"
require "utils/output"
module Homebrew
# Helper module for searching formulae or casks.
module Search
extend Utils::Output::Mixin
def self.query_regexp(query)
if (m = query.match(%r{^/(.*)/$}))
Regexp.new(m[1])

View File

@ -3,11 +3,13 @@
require "services/formula_wrapper"
require "fileutils"
require "utils/output"
module Homebrew
module Services
module Cli
extend FileUtils
extend Utils::Output::Mixin
sig { returns(T.nilable(String)) }
def self.sudo_service_user

View File

@ -3,11 +3,14 @@
require "services/cli"
require "services/formulae"
require "utils/output"
module Homebrew
module Services
module Commands
module List
extend Utils::Output::Mixin
TRIGGERS = [nil, "list", "ls"].freeze
sig { params(json: T::Boolean).void }

View File

@ -1,11 +1,15 @@
# typed: true # rubocop:todo Sorbet/StrictSigil
# frozen_string_literal: true
require "utils/output"
# Wrapper for a formula to handle service-related stuff like parsing and
# generating the service/plist files.
module Homebrew
module Services
class FormulaWrapper
include Utils::Output::Mixin
# Access the `Formula` instance.
sig { returns(Formula) }
attr_reader :formula

View File

@ -2,10 +2,13 @@
# frozen_string_literal: true
require_relative "system/systemctl"
require "utils/output"
module Homebrew
module Services
module System
extend Utils::Output::Mixin
LAUNCHCTL_DOMAIN_ACTION_NOT_SUPPORTED = T.let(125, Integer)
# Path to launchctl binary.

View File

@ -4,10 +4,12 @@
require "shellwords"
require "source_location"
require "system_command"
require "utils/output"
module Homebrew
# Helper module for running RuboCop.
module Style
extend Utils::Output::Mixin
extend SystemCommand::Mixin
# Checks style for a list of files, printing simple RuboCop output.

View File

@ -8,6 +8,7 @@ require "uri"
require "context"
require "readline_nonblock"
require "utils/timer"
require "utils/output"
# Class for running sub-processes and capturing their output and exit status.
#
@ -380,6 +381,7 @@ class SystemCommand
# Result containing the output and exit status of a finished sub-process.
class Result
include Context
include Utils::Output::Mixin
attr_accessor :command, :status, :exit_status

View File

@ -4,6 +4,7 @@
require "api"
require "commands"
require "settings"
require "utils/output"
# A {Tap} is used to encapsulate Homebrew formulae, casks and custom commands.
# Usually, it's synced with a remote Git repository. And it's likely
@ -13,6 +14,8 @@ require "settings"
# repository name without the leading `homebrew-`.
class Tap
extend Cachable
extend Utils::Output::Mixin
include Utils::Output::Mixin
HOMEBREW_TAP_CASK_RENAMES_FILE = "cask_renames.json"
private_constant :HOMEBREW_TAP_CASK_RENAMES_FILE

View File

@ -103,7 +103,6 @@ RSpec.describe Homebrew::Bundle::Dsl do
end
it ".sanitize_cask_name" do
allow_any_instance_of(Object).to receive(:opoo)
expect(described_class.send(:sanitize_cask_name, "homebrew/cask-versions/adoptopenjdk8")).to eql("adoptopenjdk8")
expect(described_class.send(:sanitize_cask_name, "adoptopenjdk8")).to eql("adoptopenjdk8")
end

View File

@ -22,9 +22,7 @@ RSpec.describe Cask::DSL, :cask, :no_api do
it "prints an error that it has encountered an unexpected method" do
expected = Regexp.compile(<<~EOS.lines.map(&:chomp).join)
(?m)
Error:
.*
Unexpected method 'future_feature' called on Cask unexpected-method-cask\\.
Error: Unexpected method 'future_feature' called on Cask unexpected-method-cask\\.
.*
https://github.com/Homebrew/homebrew-cask#reporting-bugs
EOS

View File

@ -3,92 +3,6 @@
RSpec.describe Kernel do
let(:dir) { mktmpdir }
def esc(code)
/(\e\[\d+m)*\e\[#{code}m/
end
describe "#ofail" do
it "sets Homebrew.failed to true" do
expect do
ofail "foo"
end.to output("Error: foo\n").to_stderr
expect(Homebrew).to have_failed
end
end
describe "#odie" do
it "exits with 1" do
expect do
odie "foo"
end.to output("Error: foo\n").to_stderr.and raise_error SystemExit
end
end
describe "#pretty_installed" do
subject(:pretty_installed_output) { pretty_installed("foo") }
context "when $stdout is a TTY" do
before { allow($stdout).to receive(:tty?).and_return(true) }
context "with HOMEBREW_NO_EMOJI unset" do
it "returns a string with a colored checkmark" do
expect(pretty_installed_output)
.to match(/#{esc 1}foo #{esc 32}#{esc 0}/)
end
end
context "with HOMEBREW_NO_EMOJI set" do
before { ENV["HOMEBREW_NO_EMOJI"] = "1" }
it "returns a string with colored info" do
expect(pretty_installed_output)
.to match(/#{esc 1}foo \(installed\)#{esc 0}/)
end
end
end
context "when $stdout is not a TTY" do
before { allow($stdout).to receive(:tty?).and_return(false) }
it "returns plain text" do
expect(pretty_installed_output).to eq("foo")
end
end
end
describe "#pretty_uninstalled" do
subject(:pretty_uninstalled_output) { pretty_uninstalled("foo") }
context "when $stdout is a TTY" do
before { allow($stdout).to receive(:tty?).and_return(true) }
context "with HOMEBREW_NO_EMOJI unset" do
it "returns a string with a colored checkmark" do
expect(pretty_uninstalled_output)
.to match(/#{esc 1}foo #{esc 31}#{esc 0}/)
end
end
context "with HOMEBREW_NO_EMOJI set" do
before { ENV["HOMEBREW_NO_EMOJI"] = "1" }
it "returns a string with colored info" do
expect(pretty_uninstalled_output)
.to match(/#{esc 1}foo \(uninstalled\)#{esc 0}/)
end
end
end
context "when $stdout is not a TTY" do
before { allow($stdout).to receive(:tty?).and_return(false) }
it "returns plain text" do
expect(pretty_uninstalled_output).to eq("foo")
end
end
end
describe "#interactive_shell" do
let(:shell) { dir/"myshell" }
@ -155,16 +69,6 @@ RSpec.describe Kernel do
expect(which_editor).to eq("vemate -w")
end
describe "#pretty_duration" do
it "converts seconds to a human-readable string" do
expect(pretty_duration(1)).to eq("1 second")
expect(pretty_duration(2.5)).to eq("2 seconds")
expect(pretty_duration(42)).to eq("42 seconds")
expect(pretty_duration(240)).to eq("4 minutes")
expect(pretty_duration(252.45)).to eq("4 minutes 12 seconds")
end
end
specify "#disk_usage_readable" do
expect(disk_usage_readable(1)).to eq("1B")
expect(disk_usage_readable(1000)).to eq("1000B")
@ -198,22 +102,6 @@ RSpec.describe Kernel do
expect(s).to eq(("x" * (n - glue.length)) + glue)
end
describe "#odeprecated" do
it "raises a MethodDeprecatedError when `disable` is true" do
ENV.delete("HOMEBREW_DEVELOPER")
expect do
odeprecated(
"method", "replacement",
caller: ["#{HOMEBREW_LIBRARY}/Taps/playbrew/homebrew-play/"],
disable: true
)
end.to raise_error(
MethodDeprecatedError,
%r{method.*replacement.*playbrew/homebrew-play.*/Taps/playbrew/homebrew-play/}m,
)
end
end
describe "#with_env" do
it "sets environment variables within the block" do
expect(ENV.fetch("PATH")).not_to eq("/bin")

View File

@ -2,6 +2,7 @@
# frozen_string_literal: true
require "cli/parser"
require "utils/output"
UNDEFINED_CONSTANTS = %w[
AbstractDownloadStrategy
@ -74,7 +75,7 @@ parser.parse
UNDEFINED_CONSTANTS.each do |constant_name|
Object.const_get(constant_name)
ofail "#{constant_name} should not be defined at startup"
Utils::Output.ofail "#{constant_name} should not be defined at startup"
rescue NameError
# We expect this to error as it should not be defined.
end

View File

@ -22,6 +22,9 @@ RSpec.describe Tap do
path.mkpath
(path/"audit_exceptions").mkpath
(path/"style_exceptions").mkpath
# requiring utils/output in tap.rb should be enough but it's not for no apparent reason.
$stderr.extend(Utils::Output::Mixin)
end
def setup_tap_files

View File

@ -0,0 +1,117 @@
# frozen_string_literal: true
require "utils/output"
RSpec.describe Utils::Output do
def esc(code)
/(\e\[\d+m)*\e\[#{code}m/
end
describe "#pretty_installed" do
subject(:pretty_installed_output) { described_class.pretty_installed("foo") }
context "when $stdout is a TTY" do
before { allow($stdout).to receive(:tty?).and_return(true) }
context "with HOMEBREW_NO_EMOJI unset" do
it "returns a string with a colored checkmark" do
expect(pretty_installed_output)
.to match(/#{esc 1}foo #{esc 32}#{esc 0}/)
end
end
context "with HOMEBREW_NO_EMOJI set" do
before { ENV["HOMEBREW_NO_EMOJI"] = "1" }
it "returns a string with colored info" do
expect(pretty_installed_output)
.to match(/#{esc 1}foo \(installed\)#{esc 0}/)
end
end
end
context "when $stdout is not a TTY" do
before { allow($stdout).to receive(:tty?).and_return(false) }
it "returns plain text" do
expect(pretty_installed_output).to eq("foo")
end
end
end
describe "#pretty_uninstalled" do
subject(:pretty_uninstalled_output) { described_class.pretty_uninstalled("foo") }
context "when $stdout is a TTY" do
before { allow($stdout).to receive(:tty?).and_return(true) }
context "with HOMEBREW_NO_EMOJI unset" do
it "returns a string with a colored checkmark" do
expect(pretty_uninstalled_output)
.to match(/#{esc 1}foo #{esc 31}#{esc 0}/)
end
end
context "with HOMEBREW_NO_EMOJI set" do
before { ENV["HOMEBREW_NO_EMOJI"] = "1" }
it "returns a string with colored info" do
expect(pretty_uninstalled_output)
.to match(/#{esc 1}foo \(uninstalled\)#{esc 0}/)
end
end
end
context "when $stdout is not a TTY" do
before { allow($stdout).to receive(:tty?).and_return(false) }
it "returns plain text" do
expect(pretty_uninstalled_output).to eq("foo")
end
end
end
describe "#pretty_duration" do
it "converts seconds to a human-readable string" do
expect(described_class.pretty_duration(1)).to eq("1 second")
expect(described_class.pretty_duration(2.5)).to eq("2 seconds")
expect(described_class.pretty_duration(42)).to eq("42 seconds")
expect(described_class.pretty_duration(240)).to eq("4 minutes")
expect(described_class.pretty_duration(252.45)).to eq("4 minutes 12 seconds")
end
end
describe "#ofail" do
it "sets Homebrew.failed to true" do
expect do
described_class.ofail "foo"
end.to output("Error: foo\n").to_stderr
expect(Homebrew).to have_failed
end
end
describe "#odie" do
it "exits with 1" do
expect do
described_class.odie "foo"
end.to output("Error: foo\n").to_stderr.and raise_error SystemExit
end
end
describe "#odeprecated" do
it "raises a MethodDeprecatedError when `disable` is true" do
ENV.delete("HOMEBREW_DEVELOPER")
expect do
described_class.odeprecated(
"method", "replacement",
caller: ["#{HOMEBREW_LIBRARY}/Taps/playbrew/homebrew-play/"],
disable: true
)
end.to raise_error(
MethodDeprecatedError,
%r{method.*replacement.*playbrew/homebrew-play.*/Taps/playbrew/homebrew-play/}m,
)
end
end
end

View File

@ -2,10 +2,13 @@
# frozen_string_literal: true
require "installed_dependents"
require "utils/output"
module Homebrew
# Helper module for uninstalling kegs.
module Uninstall
extend ::Utils::Output::Mixin
def self.uninstall_kegs(kegs_by_rack, casks: [], force: false, ignore_dependencies: false, named_args: [])
handle_unsatisfied_dependents(kegs_by_rack,
casks:,
@ -122,6 +125,8 @@ module Homebrew
end
class DependentsMessage
include ::Utils::Output::Mixin
attr_reader :reqs, :deps, :named_args
def initialize(requireds, dependents, named_args: [])

View File

@ -3,11 +3,14 @@
require "mktemp"
require "system_command"
require "utils/output"
# Module containing all available strategies for unpacking archives.
module UnpackStrategy
extend T::Helpers
extend Utils::Output::Mixin
include SystemCommand::Mixin
include Utils::Output::Mixin
abstract!

View File

@ -3,6 +3,7 @@
require "tempfile"
require "system_command"
require "utils/output"
module UnpackStrategy
# Strategy for unpacking disk images.
@ -12,6 +13,7 @@ module UnpackStrategy
# Helper module for listing the contents of a volume mounted from a disk image.
module Bom
extend Utils::Output::Mixin
extend SystemCommand::Mixin
DMG_METADATA = T.let(Set.new([

View File

@ -5,12 +5,14 @@ require "bundle_version"
require "cask/cask"
require "cask/installer"
require "system_command"
require "utils/output"
module Homebrew
# Check unversioned casks for updates by extracting their
# contents and guessing the version from contained files.
class UnversionedCaskChecker
include SystemCommand::Mixin
include Utils::Output::Mixin
sig { returns(Cask::Cask) }
attr_reader :cask

View File

@ -7,10 +7,13 @@ require "development_tools"
require "messages"
require "cleanup"
require "utils/topological_hash"
require "utils/output"
module Homebrew
# Helper functions for upgrading formulae.
module Upgrade
extend Utils::Output::Mixin
class Dependents < T::Struct
const :upgradeable, T::Array[Formula]
const :pinned, T::Array[Formula]

Some files were not shown because too many files have changed in this diff Show More