diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 6f5050f417..bf9a7e49b3 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -113,7 +113,6 @@ jobs: - name: Set up all Homebrew taps run: | - brew tap homebrew/aliases brew tap homebrew/bundle brew tap homebrew/command-not-found brew tap homebrew/formula-analytics @@ -129,8 +128,7 @@ jobs: homebrew/services \ homebrew/test-bot - brew style homebrew/aliases \ - homebrew/command-not-found \ + brew style homebrew/command-not-found \ homebrew/formula-analytics \ homebrew/portable-ruby diff --git a/Library/Homebrew/Gemfile.lock b/Library/Homebrew/Gemfile.lock index 615e85e794..a2ba8beff5 100644 --- a/Library/Homebrew/Gemfile.lock +++ b/Library/Homebrew/Gemfile.lock @@ -95,7 +95,7 @@ GEM rubocop (~> 1.61) rubocop-sorbet (0.8.7) rubocop (>= 1) - ruby-lsp (0.23.8) + ruby-lsp (0.23.9) language_server-protocol (~> 3.17.0) prism (>= 1.2, < 2.0) rbs (>= 3, < 4) @@ -152,7 +152,6 @@ GEM PLATFORMS aarch64-linux - arm-linux arm64-darwin x86_64-darwin x86_64-linux diff --git a/Library/Homebrew/aliases/alias.rb b/Library/Homebrew/aliases/alias.rb new file mode 100644 index 0000000000..4c93f88b7d --- /dev/null +++ b/Library/Homebrew/aliases/alias.rb @@ -0,0 +1,113 @@ +# typed: strict +# frozen_string_literal: true + +module Homebrew + module Aliases + class Alias + sig { returns(String) } + attr_accessor :name + + sig { returns(T.nilable(String)) } + attr_accessor :command + + sig { params(name: String, command: T.nilable(String)).void } + def initialize(name, command = nil) + @name = T.let(name.strip, String) + @command = T.let(nil, T.nilable(String)) + @script = T.let(nil, T.nilable(Pathname)) + @symlink = T.let(nil, T.nilable(Pathname)) + + @command = if command&.start_with?("!", "%") + command[1..] + elsif command + "brew #{command}" + end + end + + sig { returns(T::Boolean) } + def reserved? + RESERVED.include? name + end + + sig { returns(T::Boolean) } + def cmd_exists? + path = which("brew-#{name}.rb") || which("brew-#{name}") + !path.nil? && path.realpath.parent != HOMEBREW_ALIASES + end + + sig { returns(Pathname) } + def script + @script ||= Pathname.new("#{HOMEBREW_ALIASES}/#{name.gsub(/\W/, "_")}") + end + + sig { returns(Pathname) } + def symlink + @symlink ||= Pathname.new("#{HOMEBREW_PREFIX}/bin/brew-#{name}") + end + + sig { returns(T::Boolean) } + def valid_symlink? + symlink.realpath.parent == HOMEBREW_ALIASES.realpath + rescue NameError + false + end + + sig { void } + def link + FileUtils.rm symlink if File.symlink? symlink + FileUtils.ln_s script, symlink + end + + sig { params(opts: T::Hash[Symbol, T::Boolean]).void } + def write(opts = {}) + odie "'#{name}' is a reserved command. Sorry." if reserved? + odie "'brew #{name}' already exists. Sorry." if cmd_exists? + + return if !opts[:override] && script.exist? + + content = if command + <<~EOS + #: * `#{name}` [args...] + #: `brew #{name}` is an alias for `#{command}` + #{command} $* + EOS + else + <<~EOS + # + # This is a Homebrew alias script. It'll be called when the user + # types `brew #{name}`. Any remaining arguments are passed to + # this script. You can retrieve those with $*, or only the first + # one with $1. Please keep your script on one line. + + # TODO Replace the line below with your script + echo "Hello I'm brew alias "#{name}" and my args are:" $1 + EOS + end + + script.open("w") do |f| + f.write <<~EOS + #! #{`which bash`.chomp} + # alias: brew #{name} + #{content} + EOS + end + script.chmod 0744 + link + end + + sig { void } + def remove + odie "'brew #{name}' is not aliased to anything." if !symlink.exist? || !valid_symlink? + + script.unlink + symlink.unlink + end + + sig { void } + def edit + write(override: false) + exec_editor script.to_s + end + end + end +end diff --git a/Library/Homebrew/aliases/aliases.rb b/Library/Homebrew/aliases/aliases.rb new file mode 100644 index 0000000000..48887b4c4c --- /dev/null +++ b/Library/Homebrew/aliases/aliases.rb @@ -0,0 +1,77 @@ +# typed: strict +# frozen_string_literal: true + +require "aliases/alias" + +module Homebrew + module Aliases + RESERVED = T.let(( + Commands.internal_commands + + Commands.internal_developer_commands + + Commands.internal_commands_aliases + + %w[alias unalias] + ).freeze, T::Array[String]) + + sig { void } + def self.init + FileUtils.mkdir_p HOMEBREW_ALIASES + end + + sig { params(name: String, command: String).void } + def self.add(name, command) + new_alias = Alias.new(name, command) + odie "alias 'brew #{name}' already exists!" if new_alias.script.exist? + new_alias.write + end + + sig { params(name: String).void } + def self.remove(name) + Alias.new(name).remove + end + + sig { params(only: T::Array[String], block: T.proc.params(target: String, cmd: String).void).void } + def self.each(only, &block) + Dir["#{HOMEBREW_ALIASES}/*"].each do |path| + next if path.end_with? "~" # skip Emacs-like backup files + next if File.directory?(path) + + _shebang, _meta, *lines = File.readlines(path) + target = File.basename(path) + next if !only.empty? && only.exclude?(target) + + lines.reject! { |line| line.start_with?("#") || line =~ /^\s*$/ } + first_line = T.must(lines.first) + cmd = first_line.chomp + cmd.sub!(/ \$\*$/, "") + + if cmd.start_with? "brew " + cmd.sub!(/^brew /, "") + else + cmd = "!#{cmd}" + end + + yield target, cmd if block.present? + end + end + + sig { params(aliases: String).void } + def self.show(*aliases) + each([*aliases]) do |target, cmd| + puts "brew alias #{target}='#{cmd}'" + existing_alias = Alias.new(target, cmd) + existing_alias.link unless existing_alias.symlink.exist? + end + end + + sig { params(name: String, command: T.nilable(String)).void } + def self.edit(name, command = nil) + Alias.new(name, command).write unless command.nil? + Alias.new(name, command).edit + end + + sig { void } + def self.edit_all + exec_editor(*Dir[HOMEBREW_ALIASES]) + end + end +end diff --git a/Library/Homebrew/api.rb b/Library/Homebrew/api.rb index c88ef32fb5..7dcc3c696d 100644 --- a/Library/Homebrew/api.rb +++ b/Library/Homebrew/api.rb @@ -184,11 +184,6 @@ module Homebrew Tap.fetch(org, repo) end - - sig { returns(T::Boolean) } - def self.internal_json_v3? - ENV["HOMEBREW_INTERNAL_JSON_V3"].present? - end end sig { params(block: T.proc.returns(T.untyped)).returns(T.untyped) } diff --git a/Library/Homebrew/api/formula.rb b/Library/Homebrew/api/formula.rb index d905bb4c85..1bcd622da4 100644 --- a/Library/Homebrew/api/formula.rb +++ b/Library/Homebrew/api/formula.rb @@ -11,7 +11,6 @@ module Homebrew extend Cachable DEFAULT_API_FILENAME = "formula.jws.json" - INTERNAL_V3_API_FILENAME = "internal/v3/homebrew-core.jws.json" private_class_method :cache @@ -43,33 +42,24 @@ module Homebrew sig { returns(Pathname) } def self.cached_json_file_path - if Homebrew::API.internal_json_v3? - HOMEBREW_CACHE_API/INTERNAL_V3_API_FILENAME - else - HOMEBREW_CACHE_API/DEFAULT_API_FILENAME - end + HOMEBREW_CACHE_API/DEFAULT_API_FILENAME end sig { returns(T::Boolean) } def self.download_and_cache_data! - if Homebrew::API.internal_json_v3? - json_formulae, updated = Homebrew::API.fetch_json_api_file INTERNAL_V3_API_FILENAME - overwrite_cache! T.cast(json_formulae, T::Hash[String, T.untyped]) - else - json_formulae, updated = Homebrew::API.fetch_json_api_file DEFAULT_API_FILENAME + json_formulae, updated = Homebrew::API.fetch_json_api_file DEFAULT_API_FILENAME - cache["aliases"] = {} - cache["renames"] = {} - cache["formulae"] = json_formulae.to_h do |json_formula| - json_formula["aliases"].each do |alias_name| - cache["aliases"][alias_name] = json_formula["name"] - end - (json_formula["oldnames"] || [json_formula["oldname"]].compact).each do |oldname| - cache["renames"][oldname] = json_formula["name"] - end - - [json_formula["name"], json_formula.except("name")] + cache["aliases"] = {} + cache["renames"] = {} + cache["formulae"] = json_formulae.to_h do |json_formula| + json_formula["aliases"].each do |alias_name| + cache["aliases"][alias_name] = json_formula["name"] end + (json_formula["oldnames"] || [json_formula["oldname"]].compact).each do |oldname| + cache["renames"][oldname] = json_formula["name"] + end + + [json_formula["name"], json_formula.except("name")] end updated diff --git a/Library/Homebrew/brew.sh b/Library/Homebrew/brew.sh index 8f936dfc55..ee48f55738 100644 --- a/Library/Homebrew/brew.sh +++ b/Library/Homebrew/brew.sh @@ -186,27 +186,36 @@ source "${HOMEBREW_LIBRARY}/Homebrew/utils/helpers.sh" # (i.e. not defined above this line e.g. formulae or --cellar). if [[ -z "${HOMEBREW_NO_FORCE_BREW_WRAPPER:-}" && -n "${HOMEBREW_FORCE_BREW_WRAPPER:-}" ]] then + HOMEBREW_FORCE_BREW_WRAPPER_WITHOUT_BREW="${HOMEBREW_FORCE_BREW_WRAPPER%/brew}" if [[ -z "${HOMEBREW_BREW_WRAPPER:-}" ]] then odie < token, - "name" => name, - "desc" => desc, - "homepage" => homepage, - "url" => url, - "version" => version, - "sha256" => sha256, - "artifacts" => artifacts_list(compact: true), - "ruby_source_path" => ruby_source_path, - "ruby_source_sha256" => ruby_source_checksum.fetch(:sha256), - } - - if deprecation_date - api_hash["deprecation_date"] = deprecation_date - api_hash["deprecation_reason"] = deprecation_reason - api_hash["deprecation_replacement"] = deprecation_replacement - end - - if disable_date - api_hash["disable_date"] = disable_date - api_hash["disable_reason"] = disable_reason - api_hash["disable_replacement"] = disable_replacement - end - - if (url_specs_hash = url_specs).present? - api_hash["url_specs"] = url_specs_hash - end - - api_hash["caskfile_only"] = true if caskfile_only? - api_hash["conflicts_with"] = conflicts_with if conflicts_with.present? - api_hash["depends_on"] = depends_on if depends_on.present? - api_hash["container"] = container.pairs if container - api_hash["caveats"] = caveats if caveats.present? - api_hash["auto_updates"] = auto_updates if auto_updates - api_hash["languages"] = languages if languages.present? - - api_hash - end - HASH_KEYS_TO_SKIP = %w[outdated installed versions].freeze private_constant :HASH_KEYS_TO_SKIP - def to_hash_with_variations(hash_method: :to_h) - case hash_method - when :to_h - if loaded_from_api? && !Homebrew::EnvConfig.no_install_from_api? - return api_to_local_hash(Homebrew::API::Cask.all_casks[token].dup) - end - when :to_internal_api_hash - raise ArgumentError, "API Hash must be generated from Ruby source files" if loaded_from_api? - else - raise ArgumentError, "Unknown hash method #{hash_method.inspect}" + def to_hash_with_variations + if loaded_from_api? && !Homebrew::EnvConfig.no_install_from_api? + return api_to_local_hash(Homebrew::API::Cask.all_casks[token].dup) end - hash = public_send(hash_method) + hash = to_h variations = {} if @dsl.on_system_blocks_exist? @@ -471,7 +423,7 @@ module Cask Homebrew::SimulateSystem.with(os:, arch:) do refresh - public_send(hash_method).each do |key, value| + to_h.each do |key, value| next if HASH_KEYS_TO_SKIP.include? key next if value.to_s == hash[key].to_s @@ -485,11 +437,11 @@ module Cask end end - hash["variations"] = variations if hash_method != :to_internal_api_hash || variations.present? + hash["variations"] = variations hash end - def artifacts_list(compact: false, uninstall_only: false) + def artifacts_list(uninstall_only: false) artifacts.filter_map do |artifact| case artifact when Artifact::AbstractFlightBlock @@ -498,8 +450,7 @@ module Cask next if uninstall_only && !uninstall_flight_block # Only indicate whether this block is used as we don't load it from the API - # We can skip this entirely once we move to internal JSON v3. - { artifact.summarize.to_sym => nil } unless compact + { artifact.summarize.to_sym => nil } else zap_artifact = artifact.is_a?(Artifact::Zap) uninstall_artifact = artifact.respond_to?(:uninstall_phase) || artifact.respond_to?(:post_uninstall_phase) diff --git a/Library/Homebrew/cmd/alias.rb b/Library/Homebrew/cmd/alias.rb new file mode 100755 index 0000000000..85272c67de --- /dev/null +++ b/Library/Homebrew/cmd/alias.rb @@ -0,0 +1,47 @@ +# typed: strict +# frozen_string_literal: true + +require "abstract_command" +require "aliases/aliases" + +module Homebrew + module Cmd + class Alias < AbstractCommand + cmd_args do + usage_banner "`alias` [ ... | =]" + description <<~EOS + Show existing aliases. If no aliases are given, print the whole list. + EOS + switch "--edit", + description: "Edit aliases in a text editor. Either one or all aliases may be opened at once. " \ + "If the given alias doesn't exist it'll be pre-populated with a template." + named_args max: 1 + end + + sig { override.void } + def run + name = args.named.first + name, command = name.split("=", 2) if name.present? + + Aliases.init + + if name.nil? + if args.edit? + Aliases.edit_all + else + Aliases.show + end + elsif command.nil? + if args.edit? + Aliases.edit name + else + Aliases.show name + end + else + Aliases.add name, command + Aliases.edit name if args.edit? + end + end + end + end +end diff --git a/Library/Homebrew/cmd/unalias.rb b/Library/Homebrew/cmd/unalias.rb new file mode 100755 index 0000000000..a7799c6237 --- /dev/null +++ b/Library/Homebrew/cmd/unalias.rb @@ -0,0 +1,24 @@ +# typed: strict +# frozen_string_literal: true + +require "abstract_command" +require "aliases/aliases" + +module Homebrew + module Cmd + class Unalias < AbstractCommand + cmd_args do + description <<~EOS + Remove aliases. + EOS + named_args :alias, min: 1 + end + + sig { override.void } + def run + Aliases.init + args.named.each { |a| Aliases.remove a } + end + end + end +end diff --git a/Library/Homebrew/dev-cmd/generate-cask-api.rb b/Library/Homebrew/dev-cmd/generate-cask-api.rb index ac35895e3d..6f75828061 100644 --- a/Library/Homebrew/dev-cmd/generate-cask-api.rb +++ b/Library/Homebrew/dev-cmd/generate-cask-api.rb @@ -62,8 +62,6 @@ module Homebrew raise end - homebrew_cask_tap_json = JSON.generate(tap.to_internal_api_hash) - File.write("api/internal/v3/homebrew-cask.json", homebrew_cask_tap_json) unless args.dry_run? canonical_json = JSON.pretty_generate(tap.cask_renames) File.write("_data/cask_canonical.json", "#{canonical_json}\n") unless args.dry_run? end diff --git a/Library/Homebrew/dev-cmd/generate-formula-api.rb b/Library/Homebrew/dev-cmd/generate-formula-api.rb index a3fe69de0d..c87dc2a798 100644 --- a/Library/Homebrew/dev-cmd/generate-formula-api.rb +++ b/Library/Homebrew/dev-cmd/generate-formula-api.rb @@ -60,8 +60,6 @@ module Homebrew raise end - homebrew_core_tap_json = JSON.generate(tap.to_internal_api_hash) - File.write("api/internal/v3/homebrew-core.json", homebrew_core_tap_json) unless args.dry_run? canonical_json = JSON.pretty_generate(tap.formula_renames.merge(tap.alias_table)) File.write("_data/formula_canonical.json", "#{canonical_json}\n") unless args.dry_run? end diff --git a/Library/Homebrew/dev-cmd/tests.rb b/Library/Homebrew/dev-cmd/tests.rb index ac69184300..697a2a0684 100644 --- a/Library/Homebrew/dev-cmd/tests.rb +++ b/Library/Homebrew/dev-cmd/tests.rb @@ -219,7 +219,6 @@ module Homebrew # TODO: remove this and fix tests when possible. ENV["HOMEBREW_NO_INSTALL_FROM_API"] = "1" - ENV.delete("HOMEBREW_INTERNAL_JSON_V3") ENV["USER"] ||= system_command!("id", args: ["-nu"]).stdout.chomp diff --git a/Library/Homebrew/extend/cachable.rb b/Library/Homebrew/extend/cachable.rb index d3d2794bc3..b124c638e6 100644 --- a/Library/Homebrew/extend/cachable.rb +++ b/Library/Homebrew/extend/cachable.rb @@ -7,16 +7,8 @@ module Cachable @cache ||= T.let({}, T.nilable(T::Hash[T.untyped, T.untyped])) end - # NOTE: We overwrite here instead of using `Hash#clear` to handle frozen hashes. sig { void } def clear_cache - overwrite_cache!({}) - end - - private - - sig { params(hash: T::Hash[T.untyped, T.untyped]).void } - def overwrite_cache!(hash) - @cache = hash + cache.clear end end diff --git a/Library/Homebrew/formula.rb b/Library/Homebrew/formula.rb index 4dbd9124f1..889b6b6f58 100644 --- a/Library/Homebrew/formula.rb +++ b/Library/Homebrew/formula.rb @@ -2572,85 +2572,8 @@ class Formula hsh end - def to_internal_api_hash - api_hash = { - "desc" => desc, - "license" => SPDX.license_expression_to_string(license), - "homepage" => homepage, - "urls" => urls_hash.transform_values(&:compact), - "post_install_defined" => post_install_defined?, - "ruby_source_path" => ruby_source_path, - "ruby_source_sha256" => ruby_source_checksum&.hexdigest, - } - - # Exclude default values. - api_hash["revision"] = revision unless revision.zero? - api_hash["version_scheme"] = version_scheme unless version_scheme.zero? - - # Optional values. - api_hash["keg_only_reason"] = keg_only_reason.to_hash if keg_only_reason - api_hash["pour_bottle_only_if"] = self.class.pour_bottle_only_if.to_s if self.class.pour_bottle_only_if - api_hash["link_overwrite"] = self.class.link_overwrite_paths.to_a if self.class.link_overwrite_paths.present? - api_hash["caveats"] = caveats_with_placeholders if caveats - api_hash["service"] = service.to_hash if service? - - if stable - api_hash["version"] = stable&.version&.to_s - api_hash["bottle"] = bottle_hash(compact_for_api: true) if bottle_defined? - end - - if (versioned_formulae_list = versioned_formulae.presence) - # Could we just use `versioned_formulae_names` here instead? - api_hash["versioned_formulae"] = versioned_formulae_list.map(&:name) - end - - if (dependencies = internal_dependencies_hash(:stable).presence) - api_hash["dependencies"] = dependencies - end - - if (head_dependencies = internal_dependencies_hash(:head).presence) - api_hash["head_dependencies"] = head_dependencies - end - - if (requirements_array = serialized_requirements.presence) - api_hash["requirements"] = requirements_array - end - - if conflicts.present? - api_hash["conflicts_with"] = conflicts.map(&:name) - api_hash["conflicts_with_reasons"] = conflicts.map(&:reason) - end - - if deprecation_date - api_hash["deprecation_date"] = deprecation_date - api_hash["deprecation_reason"] = deprecation_reason - api_hash["deprecation_replacement"] = deprecation_replacement - end - - if disable_date - api_hash["disable_date"] = disable_date - api_hash["disable_reason"] = disable_reason - api_hash["disable_replacement"] = disable_replacement - end - - api_hash - end - - def to_hash_with_variations(hash_method: :to_hash) - if loaded_from_api? && hash_method == :to_internal_api_hash - raise ArgumentError, "API Hash must be generated from Ruby source files" - end - - namespace_prefix = case hash_method - when :to_hash - "Variations" - when :to_internal_api_hash - "APIVariations" - else - raise ArgumentError, "Unknown hash method #{hash_method.inspect}" - end - - hash = public_send(hash_method) + def to_hash_with_variations + hash = to_hash # Take from API, merging in local install status. if loaded_from_api? && !Homebrew::EnvConfig.no_install_from_api? @@ -2669,13 +2592,13 @@ class Formula next unless bottle_tag.valid_combination? Homebrew::SimulateSystem.with(os:, arch:) do - variations_namespace = Formulary.class_s("#{namespace_prefix}#{bottle_tag.to_sym.capitalize}") + variations_namespace = Formulary.class_s("Variations#{bottle_tag.to_sym.capitalize}") variations_formula_class = Formulary.load_formula(name, path, formula_contents, variations_namespace, flags: self.class.build_flags, ignore_errors: true) variations_formula = variations_formula_class.new(name, path, :stable, alias_path:, force_bottle:) - variations_formula.public_send(hash_method).each do |key, value| + variations_formula.to_hash.each do |key, value| next if value.to_s == hash[key].to_s variations[bottle_tag.to_sym] ||= {} @@ -2685,12 +2608,12 @@ class Formula end end - hash["variations"] = variations if hash_method != :to_internal_api_hash || variations.present? + hash["variations"] = variations hash end # Returns the bottle information for a formula. - def bottle_hash(compact_for_api: false) + def bottle_hash hash = {} stable_spec = stable return hash unless stable_spec @@ -2698,8 +2621,8 @@ class Formula bottle_spec = stable_spec.bottle_specification - hash["rebuild"] = bottle_spec.rebuild if !compact_for_api || !bottle_spec.rebuild.zero? - hash["root_url"] = bottle_spec.root_url unless compact_for_api + hash["rebuild"] = bottle_spec.rebuild + hash["root_url"] = bottle_spec.root_url hash["files"] = {} bottle_spec.collector.each_tag do |tag| @@ -2710,11 +2633,9 @@ class Formula file_hash = {} file_hash["cellar"] = os_cellar - unless compact_for_api - filename = Bottle::Filename.create(self, tag, bottle_spec.rebuild) - path, = Utils::Bottles.path_resolved_basename(bottle_spec.root_url, name, checksum, filename) - file_hash["url"] = "#{bottle_spec.root_url}/#{path}" - end + filename = Bottle::Filename.create(self, tag, bottle_spec.rebuild) + path, = Utils::Bottles.path_resolved_basename(bottle_spec.root_url, name, checksum, filename) + file_hash["url"] = "#{bottle_spec.root_url}/#{path}" file_hash["sha256"] = checksum hash["files"][tag.to_sym] = file_hash diff --git a/Library/Homebrew/formulary.rb b/Library/Homebrew/formulary.rb index 0529671e9a..5dd9e2ec4c 100644 --- a/Library/Homebrew/formulary.rb +++ b/Library/Homebrew/formulary.rb @@ -213,71 +213,38 @@ module Formulary end end - add_deps = if Homebrew::API.internal_json_v3? - lambda do |deps| - T.bind(self, SoftwareSpec) + add_deps = lambda do |spec| + T.bind(self, SoftwareSpec) - deps&.each do |name, info| - tags = case info&.dig("tags") - in Array => tag_list - tag_list.map(&:to_sym) - in String => tag - tag.to_sym - else - nil - end + dep_json = json_formula.fetch("#{spec}_dependencies", json_formula) - if info&.key?("uses_from_macos") - bounds = info["uses_from_macos"].dup || {} - bounds.deep_transform_keys!(&:to_sym) - bounds.deep_transform_values!(&:to_sym) + dep_json["dependencies"]&.each do |dep| + # Backwards compatibility check - uses_from_macos used to be a part of dependencies on Linux + next if !json_formula.key?("uses_from_macos_bounds") && uses_from_macos_names.include?(dep) && + !Homebrew::SimulateSystem.simulating_or_running_on_macos? - if tags - uses_from_macos name => tags, **bounds - else - uses_from_macos name, **bounds - end - elsif tags - depends_on name => tags - else - depends_on name - end - end + depends_on dep end - else - lambda do |spec| - T.bind(self, SoftwareSpec) - dep_json = json_formula.fetch("#{spec}_dependencies", json_formula) - - dep_json["dependencies"]&.each do |dep| + [:build, :test, :recommended, :optional].each do |type| + dep_json["#{type}_dependencies"]&.each do |dep| # Backwards compatibility check - uses_from_macos used to be a part of dependencies on Linux next if !json_formula.key?("uses_from_macos_bounds") && uses_from_macos_names.include?(dep) && !Homebrew::SimulateSystem.simulating_or_running_on_macos? - depends_on dep + depends_on dep => type end + end - [:build, :test, :recommended, :optional].each do |type| - dep_json["#{type}_dependencies"]&.each do |dep| - # Backwards compatibility check - uses_from_macos used to be a part of dependencies on Linux - next if !json_formula.key?("uses_from_macos_bounds") && uses_from_macos_names.include?(dep) && - !Homebrew::SimulateSystem.simulating_or_running_on_macos? + dep_json["uses_from_macos"]&.each_with_index do |dep, index| + bounds = dep_json.fetch("uses_from_macos_bounds", [])[index].dup || {} + bounds.deep_transform_keys!(&:to_sym) + bounds.deep_transform_values!(&:to_sym) - depends_on dep => type - end - end - - dep_json["uses_from_macos"]&.each_with_index do |dep, index| - bounds = dep_json.fetch("uses_from_macos_bounds", [])[index].dup || {} - bounds.deep_transform_keys!(&:to_sym) - bounds.deep_transform_values!(&:to_sym) - - if dep.is_a?(Hash) - uses_from_macos dep.deep_transform_values(&:to_sym).merge(bounds) - else - uses_from_macos dep, bounds - end + if dep.is_a?(Hash) + uses_from_macos dep.deep_transform_values(&:to_sym).merge(bounds) + else + uses_from_macos dep, bounds end end end @@ -299,15 +266,10 @@ module Formulary using: urls_stable["using"]&.to_sym, }.compact url urls_stable["url"], **url_spec - version Homebrew::API.internal_json_v3? ? json_formula["version"] : json_formula["versions"]["stable"] + version json_formula["versions"]["stable"] sha256 urls_stable["checksum"] if urls_stable["checksum"].present? - if Homebrew::API.internal_json_v3? - instance_exec(json_formula["dependencies"], &add_deps) - else - instance_exec(:stable, &add_deps) - end - + instance_exec(:stable, &add_deps) requirements[:stable]&.each do |req| depends_on req end @@ -322,23 +284,14 @@ module Formulary }.compact url urls_head["url"], **url_spec - if Homebrew::API.internal_json_v3? - instance_exec(json_formula["head_dependencies"], &add_deps) - else - instance_exec(:head, &add_deps) - end - + instance_exec(:head, &add_deps) requirements[:head]&.each do |req| depends_on req end end end - bottles_stable = if Homebrew::API.internal_json_v3? - json_formula["bottle"] - else - json_formula["bottle"]["stable"] - end.presence + bottles_stable = json_formula["bottle"]["stable"].presence if bottles_stable bottle do @@ -426,26 +379,20 @@ module Formulary .gsub(HOMEBREW_HOME_PLACEHOLDER, Dir.home) end - @tap_git_head_string = if Homebrew::API.internal_json_v3? - Homebrew::API::Formula.tap_git_head - else - json_formula["tap_git_head"] - end + @tap_git_head_string = json_formula["tap_git_head"] def tap_git_head self.class.instance_variable_get(:@tap_git_head_string) end - unless Homebrew::API.internal_json_v3? - @oldnames_array = json_formula["oldnames"] || [json_formula["oldname"]].compact - def oldnames - self.class.instance_variable_get(:@oldnames_array) - end + @oldnames_array = json_formula["oldnames"] || [json_formula["oldname"]].compact + def oldnames + self.class.instance_variable_get(:@oldnames_array) + end - @aliases_array = json_formula.fetch("aliases", []) - def aliases - self.class.instance_variable_get(:@aliases_array) - end + @aliases_array = json_formula.fetch("aliases", []) + def aliases + self.class.instance_variable_get(:@aliases_array) end @versioned_formulae_array = json_formula.fetch("versioned_formulae", []) diff --git a/Library/Homebrew/livecheck.rb b/Library/Homebrew/livecheck.rb index 0dc87c91b6..ebfcc7ec52 100644 --- a/Library/Homebrew/livecheck.rb +++ b/Library/Homebrew/livecheck.rb @@ -20,6 +20,14 @@ class Livecheck sig { returns(T.nilable(String)) } attr_reader :skip_msg + # A block used by strategies to identify version information. + sig { returns(T.nilable(Proc)) } + attr_reader :strategy_block + + # Options used by `Strategy` methods to modify `curl` behavior. + sig { returns(T.nilable(T::Hash[Symbol, T.untyped])) } + attr_reader :url_options + sig { params(package_or_resource: T.any(Cask::Cask, T.class_of(Formula), Resource)).void } def initialize(package_or_resource) @package_or_resource = package_or_resource @@ -32,6 +40,7 @@ class Livecheck @strategy_block = T.let(nil, T.nilable(Proc)) @throttle = T.let(nil, T.nilable(Integer)) @url = T.let(nil, T.any(NilClass, String, Symbol)) + @url_options = T.let(nil, T.nilable(T::Hash[Symbol, T.untyped])) end # Sets the `@referenced_cask_name` instance variable to the provided `String` @@ -134,9 +143,6 @@ class Livecheck end end - sig { returns(T.nilable(Proc)) } - attr_reader :strategy_block - # Sets the `@throttle` instance variable to the provided `Integer` or returns # the `@throttle` instance variable when no argument is provided. sig { @@ -158,13 +164,22 @@ class Livecheck # `@url` instance variable when no argument is provided. The argument can be # a `String` (a URL) or a supported `Symbol` corresponding to a URL in the # formula/cask/resource (e.g. `:stable`, `:homepage`, `:head`, `:url`). + # Any options provided to the method are passed through to `Strategy` methods + # (`page_headers`, `page_content`). sig { params( # URL to check for version information. - url: T.any(String, Symbol), + url: T.any(String, Symbol), + post_form: T.nilable(T::Hash[T.any(String, Symbol), String]), + post_json: T.nilable(T::Hash[T.any(String, Symbol), String]), ).returns(T.nilable(T.any(String, Symbol))) } - def url(url = T.unsafe(nil)) + def url(url = T.unsafe(nil), post_form: nil, post_json: nil) + raise ArgumentError, "Only use `post_form` or `post_json`, not both" if post_form && post_json + + options = { post_form:, post_json: }.compact + @url_options = options if options.present? + case url when nil @url @@ -183,14 +198,15 @@ class Livecheck sig { returns(T::Hash[String, T.untyped]) } def to_hash { - "cask" => @referenced_cask_name, - "formula" => @referenced_formula_name, - "regex" => @regex, - "skip" => @skip, - "skip_msg" => @skip_msg, - "strategy" => @strategy, - "throttle" => @throttle, - "url" => @url, + "cask" => @referenced_cask_name, + "formula" => @referenced_formula_name, + "regex" => @regex, + "skip" => @skip, + "skip_msg" => @skip_msg, + "strategy" => @strategy, + "throttle" => @throttle, + "url" => @url, + "url_options" => @url_options, } end end diff --git a/Library/Homebrew/livecheck/livecheck.rb b/Library/Homebrew/livecheck/livecheck.rb index 609e830941..4c0832dec0 100644 --- a/Library/Homebrew/livecheck/livecheck.rb +++ b/Library/Homebrew/livecheck/livecheck.rb @@ -614,6 +614,7 @@ module Homebrew referenced_livecheck = referenced_formula_or_cask&.livecheck livecheck_url = livecheck.url || referenced_livecheck&.url + livecheck_url_options = livecheck.url_options || referenced_livecheck&.url_options livecheck_regex = livecheck.regex || referenced_livecheck&.regex livecheck_strategy = livecheck.strategy || referenced_livecheck&.strategy livecheck_strategy_block = livecheck.strategy_block || referenced_livecheck&.strategy_block @@ -673,6 +674,7 @@ module Homebrew elsif original_url.present? && original_url != "None" puts "URL: #{original_url}" end + puts "URL Options: #{livecheck_url_options}" if livecheck_url_options.present? puts "URL (processed): #{url}" if url != original_url if strategies.present? && verbose puts "Strategies: #{strategies.map { |s| livecheck_strategy_names[s] }.join(", ")}" @@ -701,6 +703,7 @@ module Homebrew strategy_args = { regex: livecheck_regex, + url_options: livecheck_url_options, homebrew_curl:, } # TODO: Set `cask`/`url` args based on the presence of the keyword arg @@ -807,6 +810,7 @@ module Homebrew version_info[:meta][:url][:strategy] = strategy_data[:url] end version_info[:meta][:url][:final] = strategy_data[:final_url] if strategy_data[:final_url] + version_info[:meta][:url][:options] = livecheck_url_options if livecheck_url_options.present? version_info[:meta][:url][:homebrew_curl] = homebrew_curl if homebrew_curl.present? end version_info[:meta][:strategy] = strategy_name if strategy.present? @@ -856,6 +860,7 @@ module Homebrew livecheck = resource.livecheck livecheck_reference = livecheck.formula livecheck_url = livecheck.url + livecheck_url_options = livecheck.url_options livecheck_regex = livecheck.regex livecheck_strategy = livecheck.strategy livecheck_strategy_block = livecheck.strategy_block @@ -893,6 +898,7 @@ module Homebrew elsif original_url.present? && original_url != "None" puts "URL: #{original_url}" end + puts "URL Options: #{livecheck_url_options}" if livecheck_url_options.present? puts "URL (processed): #{url}" if url != original_url if strategies.present? && verbose puts "Strategies: #{strategies.map { |s| livecheck_strategy_names[s] }.join(", ")}" @@ -923,6 +929,7 @@ module Homebrew strategy_args = { url:, regex: livecheck_regex, + url_options: livecheck_url_options, homebrew_curl: false, }.compact @@ -1012,6 +1019,7 @@ module Homebrew resource_version_info[:meta][:url][:strategy] = strategy_data[:url] end resource_version_info[:meta][:url][:final] = strategy_data[:final_url] if strategy_data&.dig(:final_url) + resource_version_info[:meta][:url][:options] = livecheck_url_options if livecheck_url_options.present? end resource_version_info[:meta][:strategy] = strategy_name if strategy.present? if strategies.present? diff --git a/Library/Homebrew/livecheck/strategy.rb b/Library/Homebrew/livecheck/strategy.rb index be885df754..950c8578e7 100644 --- a/Library/Homebrew/livecheck/strategy.rb +++ b/Library/Homebrew/livecheck/strategy.rb @@ -166,20 +166,59 @@ module Homebrew end end + # Creates `curl` `--data` or `--json` arguments (for `POST` requests`) + # from related `livecheck` block `url` options. + # + # @param post_form [Hash, nil] data to encode using `URI::encode_www_form` + # @param post_json [Hash, nil] data to encode using `JSON::generate` + # @return [Array] + sig { + params( + post_form: T.nilable(T::Hash[T.any(String, Symbol), String]), + post_json: T.nilable(T::Hash[T.any(String, Symbol), String]), + ).returns(T::Array[String]) + } + def post_args(post_form: nil, post_json: nil) + if post_form.present? + require "uri" + ["--data", URI.encode_www_form(post_form)] + elsif post_json.present? + require "json" + ["--json", JSON.generate(post_json)] + else + [] + end + end + # Collects HTTP response headers, starting with the provided URL. # Redirections will be followed and all the response headers are # collected into an array of hashes. # # @param url [String] the URL to fetch + # @param url_options [Hash] options to modify curl behavior # @param homebrew_curl [Boolean] whether to use brewed curl with the URL # @return [Array] - sig { params(url: String, homebrew_curl: T::Boolean).returns(T::Array[T::Hash[String, String]]) } - def self.page_headers(url, homebrew_curl: false) + sig { + params( + url: String, + url_options: T::Hash[Symbol, T.untyped], + homebrew_curl: T::Boolean, + ).returns(T::Array[T::Hash[String, String]]) + } + def self.page_headers(url, url_options: {}, homebrew_curl: false) headers = [] + if url_options[:post_form].present? || url_options[:post_json].present? + curl_post_args = ["--request", "POST", *post_args( + post_form: url_options[:post_form], + post_json: url_options[:post_json], + )] + end + [:default, :browser].each do |user_agent| begin parsed_output = curl_headers( + *curl_post_args, "--max-redirs", MAX_REDIRECTIONS.to_s, url, @@ -205,13 +244,28 @@ module Homebrew # array with the error message instead. # # @param url [String] the URL of the content to check + # @param url_options [Hash] options to modify curl behavior # @param homebrew_curl [Boolean] whether to use brewed curl with the URL # @return [Hash] - sig { params(url: String, homebrew_curl: T::Boolean).returns(T::Hash[Symbol, T.untyped]) } - def self.page_content(url, homebrew_curl: false) + sig { + params( + url: String, + url_options: T::Hash[Symbol, T.untyped], + homebrew_curl: T::Boolean, + ).returns(T::Hash[Symbol, T.untyped]) + } + def self.page_content(url, url_options: {}, homebrew_curl: false) + if url_options[:post_form].present? || url_options[:post_json].present? + curl_post_args = ["--request", "POST", *post_args( + post_form: url_options[:post_form], + post_json: url_options[:post_json], + )] + end + stderr = T.let(nil, T.nilable(String)) [:default, :browser].each do |user_agent| stdout, stderr, status = curl_output( + *curl_post_args, *PAGE_CONTENT_CURL_ARGS, url, **DEFAULT_CURL_OPTIONS, use_homebrew_curl: homebrew_curl || !curl_supports_fail_with_body?, diff --git a/Library/Homebrew/livecheck/strategy.rbi b/Library/Homebrew/livecheck/strategy.rbi new file mode 100644 index 0000000000..218530f2c3 --- /dev/null +++ b/Library/Homebrew/livecheck/strategy.rbi @@ -0,0 +1,9 @@ +# typed: strict + +module Homebrew + module Livecheck + module Strategy + include Kernel + end + end +end diff --git a/Library/Homebrew/livecheck/strategy/crate.rb b/Library/Homebrew/livecheck/strategy/crate.rb index 7c6f942353..e37e3da27d 100644 --- a/Library/Homebrew/livecheck/strategy/crate.rb +++ b/Library/Homebrew/livecheck/strategy/crate.rb @@ -81,11 +81,11 @@ module Homebrew regex: T.nilable(Regexp), provided_content: T.nilable(String), homebrew_curl: T::Boolean, - _unused: T.untyped, + unused: T.untyped, block: T.nilable(Proc), ).returns(T::Hash[Symbol, T.untyped]) } - def self.find_versions(url:, regex: nil, provided_content: nil, homebrew_curl: false, **_unused, &block) + def self.find_versions(url:, regex: nil, provided_content: nil, homebrew_curl: false, **unused, &block) match_data = { matches: {}, regex:, url: } match_data[:cached] = true if provided_content.is_a?(String) @@ -97,7 +97,13 @@ module Homebrew content = if provided_content provided_content else - match_data.merge!(Strategy.page_content(match_data[:url], homebrew_curl:)) + match_data.merge!( + Strategy.page_content( + match_data[:url], + url_options: unused.fetch(:url_options, {}), + homebrew_curl:, + ), + ) match_data[:content] end return match_data unless content diff --git a/Library/Homebrew/livecheck/strategy/header_match.rb b/Library/Homebrew/livecheck/strategy/header_match.rb index 460f8025f7..e6f2c3a3aa 100644 --- a/Library/Homebrew/livecheck/strategy/header_match.rb +++ b/Library/Homebrew/livecheck/strategy/header_match.rb @@ -74,14 +74,18 @@ module Homebrew url: String, regex: T.nilable(Regexp), homebrew_curl: T::Boolean, - _unused: T.untyped, + unused: T.untyped, block: T.nilable(Proc), ).returns(T::Hash[Symbol, T.untyped]) } - def self.find_versions(url:, regex: nil, homebrew_curl: false, **_unused, &block) + def self.find_versions(url:, regex: nil, homebrew_curl: false, **unused, &block) match_data = { matches: {}, regex:, url: } - headers = Strategy.page_headers(url, homebrew_curl:) + headers = Strategy.page_headers( + url, + url_options: unused.fetch(:url_options, {}), + homebrew_curl:, + ) # Merge the headers from all responses into one hash merged_headers = headers.reduce(&:merge) diff --git a/Library/Homebrew/livecheck/strategy/json.rb b/Library/Homebrew/livecheck/strategy/json.rb index b8f05e29ae..e7ac5e51d4 100644 --- a/Library/Homebrew/livecheck/strategy/json.rb +++ b/Library/Homebrew/livecheck/strategy/json.rb @@ -102,11 +102,11 @@ module Homebrew regex: T.nilable(Regexp), provided_content: T.nilable(String), homebrew_curl: T::Boolean, - _unused: T.untyped, + unused: T.untyped, block: T.nilable(Proc), ).returns(T::Hash[Symbol, T.untyped]) } - def self.find_versions(url:, regex: nil, provided_content: nil, homebrew_curl: false, **_unused, &block) + def self.find_versions(url:, regex: nil, provided_content: nil, homebrew_curl: false, **unused, &block) raise ArgumentError, "#{Utils.demodulize(T.must(name))} requires a `strategy` block" if block.blank? match_data = { matches: {}, regex:, url: } @@ -116,7 +116,13 @@ module Homebrew match_data[:cached] = true provided_content else - match_data.merge!(Strategy.page_content(url, homebrew_curl:)) + match_data.merge!( + Strategy.page_content( + url, + url_options: unused.fetch(:url_options, {}), + homebrew_curl:, + ), + ) match_data[:content] end return match_data if content.blank? diff --git a/Library/Homebrew/livecheck/strategy/page_match.rb b/Library/Homebrew/livecheck/strategy/page_match.rb index 8f68676b54..7f2daaae1d 100644 --- a/Library/Homebrew/livecheck/strategy/page_match.rb +++ b/Library/Homebrew/livecheck/strategy/page_match.rb @@ -85,11 +85,11 @@ module Homebrew regex: T.nilable(Regexp), provided_content: T.nilable(String), homebrew_curl: T::Boolean, - _unused: T.untyped, + unused: T.untyped, block: T.nilable(Proc), ).returns(T::Hash[Symbol, T.untyped]) } - def self.find_versions(url:, regex: nil, provided_content: nil, homebrew_curl: false, **_unused, &block) + def self.find_versions(url:, regex: nil, provided_content: nil, homebrew_curl: false, **unused, &block) if regex.blank? && block.blank? raise ArgumentError, "#{Utils.demodulize(T.must(name))} requires a regex or `strategy` block" end @@ -101,7 +101,13 @@ module Homebrew match_data[:cached] = true provided_content else - match_data.merge!(Strategy.page_content(url, homebrew_curl:)) + match_data.merge!( + Strategy.page_content( + url, + url_options: unused.fetch(:url_options, {}), + homebrew_curl:, + ), + ) match_data[:content] end return match_data if content.blank? diff --git a/Library/Homebrew/livecheck/strategy/sparkle.rb b/Library/Homebrew/livecheck/strategy/sparkle.rb index de16eaec18..6a298a14ae 100644 --- a/Library/Homebrew/livecheck/strategy/sparkle.rb +++ b/Library/Homebrew/livecheck/strategy/sparkle.rb @@ -214,16 +214,18 @@ module Homebrew # # @param url [String] the URL of the content to check # @param regex [Regexp, nil] a regex for use in a strategy block + # @param homebrew_curl [Boolean] whether to use brewed curl with the URL # @return [Hash] sig { params( - url: String, - regex: T.nilable(Regexp), - _unused: T.untyped, - block: T.nilable(Proc), + url: String, + regex: T.nilable(Regexp), + homebrew_curl: T::Boolean, + unused: T.untyped, + block: T.nilable(Proc), ).returns(T::Hash[Symbol, T.untyped]) } - def self.find_versions(url:, regex: nil, **_unused, &block) + def self.find_versions(url:, regex: nil, homebrew_curl: false, **unused, &block) if regex.present? && block.blank? raise ArgumentError, "#{Utils.demodulize(T.must(name))} only supports a regex when using a `strategy` block" @@ -231,7 +233,13 @@ module Homebrew match_data = { matches: {}, regex:, url: } - match_data.merge!(Strategy.page_content(url)) + match_data.merge!( + Strategy.page_content( + url, + url_options: unused.fetch(:url_options, {}), + homebrew_curl:, + ), + ) content = match_data.delete(:content) return match_data if content.blank? diff --git a/Library/Homebrew/livecheck/strategy/xml.rb b/Library/Homebrew/livecheck/strategy/xml.rb index e8b741e9ff..f0dfffa13e 100644 --- a/Library/Homebrew/livecheck/strategy/xml.rb +++ b/Library/Homebrew/livecheck/strategy/xml.rb @@ -142,11 +142,11 @@ module Homebrew regex: T.nilable(Regexp), provided_content: T.nilable(String), homebrew_curl: T::Boolean, - _unused: T.untyped, + unused: T.untyped, block: T.nilable(Proc), ).returns(T::Hash[Symbol, T.untyped]) } - def self.find_versions(url:, regex: nil, provided_content: nil, homebrew_curl: false, **_unused, &block) + def self.find_versions(url:, regex: nil, provided_content: nil, homebrew_curl: false, **unused, &block) raise ArgumentError, "#{Utils.demodulize(T.must(name))} requires a `strategy` block" if block.blank? match_data = { matches: {}, regex:, url: } @@ -156,7 +156,13 @@ module Homebrew match_data[:cached] = true provided_content else - match_data.merge!(Strategy.page_content(url, homebrew_curl:)) + match_data.merge!( + Strategy.page_content( + url, + url_options: unused.fetch(:url_options, {}), + homebrew_curl:, + ), + ) match_data[:content] end return match_data if content.blank? diff --git a/Library/Homebrew/livecheck/strategy/yaml.rb b/Library/Homebrew/livecheck/strategy/yaml.rb index 371128a7a9..e3feaf02e5 100644 --- a/Library/Homebrew/livecheck/strategy/yaml.rb +++ b/Library/Homebrew/livecheck/strategy/yaml.rb @@ -102,11 +102,11 @@ module Homebrew regex: T.nilable(Regexp), provided_content: T.nilable(String), homebrew_curl: T::Boolean, - _unused: T.untyped, + unused: T.untyped, block: T.nilable(Proc), ).returns(T::Hash[Symbol, T.untyped]) } - def self.find_versions(url:, regex: nil, provided_content: nil, homebrew_curl: false, **_unused, &block) + def self.find_versions(url:, regex: nil, provided_content: nil, homebrew_curl: false, **unused, &block) raise ArgumentError, "#{Utils.demodulize(T.must(name))} requires a `strategy` block" if block.blank? match_data = { matches: {}, regex:, url: } @@ -116,7 +116,13 @@ module Homebrew match_data[:cached] = true provided_content else - match_data.merge!(Strategy.page_content(url, homebrew_curl:)) + match_data.merge!( + Strategy.page_content( + url, + url_options: unused.fetch(:url_options, {}), + homebrew_curl:, + ), + ) match_data[:content] end return match_data if content.blank? diff --git a/Library/Homebrew/official_taps.rb b/Library/Homebrew/official_taps.rb index 65c0473342..db5e78c32f 100644 --- a/Library/Homebrew/official_taps.rb +++ b/Library/Homebrew/official_taps.rb @@ -6,7 +6,6 @@ OFFICIAL_CASK_TAPS = %w[ ].freeze OFFICIAL_CMD_TAPS = T.let({ - "homebrew/aliases" => ["alias", "unalias"], "homebrew/bundle" => ["bundle"], "homebrew/command-not-found" => ["command-not-found-init", "which-formula", "which-update"], "homebrew/test-bot" => ["test-bot"], @@ -14,6 +13,7 @@ OFFICIAL_CMD_TAPS = T.let({ }.freeze, T::Hash[String, T::Array[String]]) DEPRECATED_OFFICIAL_TAPS = %w[ + aliases apache binary cask-drivers diff --git a/Library/Homebrew/sorbet/rbi/dsl/homebrew/cmd/alias.rbi b/Library/Homebrew/sorbet/rbi/dsl/homebrew/cmd/alias.rbi new file mode 100644 index 0000000000..f3d1819f35 --- /dev/null +++ b/Library/Homebrew/sorbet/rbi/dsl/homebrew/cmd/alias.rbi @@ -0,0 +1,16 @@ +# typed: true + +# DO NOT EDIT MANUALLY +# This is an autogenerated file for dynamic methods in `Homebrew::Cmd::Alias`. +# Please instead update this file by running `bin/tapioca dsl Homebrew::Cmd::Alias`. + + +class Homebrew::Cmd::Alias + sig { returns(Homebrew::Cmd::Alias::Args) } + def args; end +end + +class Homebrew::Cmd::Alias::Args < Homebrew::CLI::Args + sig { returns(T::Boolean) } + def edit?; end +end diff --git a/Library/Homebrew/sorbet/rbi/dsl/homebrew/cmd/unalias.rbi b/Library/Homebrew/sorbet/rbi/dsl/homebrew/cmd/unalias.rbi new file mode 100644 index 0000000000..0027911431 --- /dev/null +++ b/Library/Homebrew/sorbet/rbi/dsl/homebrew/cmd/unalias.rbi @@ -0,0 +1,13 @@ +# typed: true + +# DO NOT EDIT MANUALLY +# This is an autogenerated file for dynamic methods in `Homebrew::Cmd::Unalias`. +# Please instead update this file by running `bin/tapioca dsl Homebrew::Cmd::Unalias`. + + +class Homebrew::Cmd::Unalias + sig { returns(Homebrew::Cmd::Unalias::Args) } + def args; end +end + +class Homebrew::Cmd::Unalias::Args < Homebrew::CLI::Args; end diff --git a/Library/Homebrew/startup/config.rb b/Library/Homebrew/startup/config.rb index 225a3a04a7..9a14eb095c 100644 --- a/Library/Homebrew/startup/config.rb +++ b/Library/Homebrew/startup/config.rb @@ -65,3 +65,15 @@ HOMEBREW_RUBY_EXEC_ARGS = [ ENV.fetch("HOMEBREW_RUBY_WARNINGS"), ENV.fetch("HOMEBREW_RUBY_DISABLE_OPTIONS"), ].freeze + +# Location for `brew alias` and `brew unalias` commands. +# +# Unix-Like systems store config in $HOME/.config whose location can be +# overridden by the XDG_CONFIG_HOME environment variable. Unfortunately +# Homebrew strictly filters environment variables in BuildEnvironment. +HOMEBREW_ALIASES = if (path = Pathname.new("~/.config/brew-aliases").expand_path).exist? || + (path = Pathname.new("~/.brew-aliases").expand_path).exist? + path.realpath +else + path +end.freeze diff --git a/Library/Homebrew/tap.rb b/Library/Homebrew/tap.rb index 52794cb99b..a00f95b9e1 100644 --- a/Library/Homebrew/tap.rb +++ b/Library/Homebrew/tap.rb @@ -1307,8 +1307,6 @@ class CoreTap < AbstractCoreTap @tap_migrations ||= if Homebrew::EnvConfig.no_install_from_api? ensure_installed! super - elsif Homebrew::API.internal_json_v3? - Homebrew::API::Formula.tap_migrations else migrations, = Homebrew::API.fetch_json_api_file "formula_tap_migrations.jws.json", stale_seconds: TAP_MIGRATIONS_STALE_SECONDS @@ -1400,23 +1398,6 @@ class CoreTap < AbstractCoreTap end end end - - sig { returns(T::Hash[String, T.untyped]) } - def to_internal_api_hash - formulae_api_hash = formula_names.to_h do |name| - formula = Formulary.factory(name) - formula_hash = formula.to_hash_with_variations(hash_method: :to_internal_api_hash) - [name, formula_hash] - end - - { - "tap_git_head" => git_head, - "aliases" => alias_table, - "renames" => formula_renames, - "tap_migrations" => tap_migrations, - "formulae" => formulae_api_hash, - } - end end # A specialized {Tap} class for homebrew-cask. @@ -1491,22 +1472,6 @@ class CoreCaskTap < AbstractCoreTap migrations end end - - sig { returns(T::Hash[String, T.untyped]) } - def to_internal_api_hash - casks_api_hash = cask_tokens.to_h do |token| - cask = Cask::CaskLoader.load(token) - cask_hash = cask.to_hash_with_variations(hash_method: :to_internal_api_hash) - [token, cask_hash] - end - - { - "tap_git_head" => git_head, - "renames" => cask_renames, - "tap_migrations" => tap_migrations, - "casks" => casks_api_hash, - } - end end # Permanent configuration per {Tap} using `git-config(1)`. diff --git a/Library/Homebrew/test/.brew-aliases/foo b/Library/Homebrew/test/.brew-aliases/foo new file mode 100755 index 0000000000..2e3286c7a0 --- /dev/null +++ b/Library/Homebrew/test/.brew-aliases/foo @@ -0,0 +1,6 @@ +#! /bin/bash +# alias: brew foo +#: * `foo` [args...] +#: `brew foo` is an alias for `brew bar` +brew bar $* + diff --git a/Library/Homebrew/test/api/internal_tap_json/formula_spec.rb b/Library/Homebrew/test/api/internal_tap_json/formula_spec.rb deleted file mode 100644 index 6e79402e99..0000000000 --- a/Library/Homebrew/test/api/internal_tap_json/formula_spec.rb +++ /dev/null @@ -1,170 +0,0 @@ -# frozen_string_literal: true - -RSpec.describe "Internal Tap JSON -- Formula", type: :system do - include FileUtils - - let(:internal_tap_json) { File.read(TEST_FIXTURE_DIR/"internal_tap_json/homebrew-core.json").chomp } - let(:tap_git_head) { "9977471165641744a829d3e494fa563407503297" } - - context "when generating JSON", :needs_macos do - before do - cp_r(TEST_FIXTURE_DIR/"internal_tap_json/homebrew-core", HOMEBREW_TAP_DIRECTORY/"homebrew") - - # NOTE: Symlinks can't be copied recursively so we create them manually here. - (HOMEBREW_TAP_DIRECTORY/"homebrew/homebrew-core").tap do |core_tap| - mkdir(core_tap/"Aliases") - ln_s(core_tap/"Formula/f/fennel.rb", core_tap/"Aliases/fennel-lang") - ln_s(core_tap/"Formula/p/ponyc.rb", core_tap/"Aliases/ponyc-lang") - end - end - - it "creates the expected hash" do - api_hash = CoreTap.instance.to_internal_api_hash - api_hash["tap_git_head"] = tap_git_head # tricky to mock - - expect(JSON.pretty_generate(api_hash)).to eq(internal_tap_json) - end - end - - context "when loading JSON" do - before do - ENV["HOMEBREW_INTERNAL_JSON_V3"] = "1" - ENV.delete("HOMEBREW_NO_INSTALL_FROM_API") - - allow(Homebrew::API).to receive(:fetch_json_api_file) - .with("internal/v3/homebrew-core.jws.json") - .and_return([JSON.parse(internal_tap_json, freeze: true), false]) - - # `Tap.tap_migration_oldnames` looks for renames in every - # tap so `CoreCaskTap.tap_migrations` gets called and tries to - # fetch stuff from the API. This just avoids errors. - allow(Homebrew::API).to receive(:fetch_json_api_file) - .with("cask_tap_migrations.jws.json", anything) - .and_return([{}, false]) - - # To allow `formula_names.txt` to be written to the cache. - (HOMEBREW_CACHE/"api").mkdir - end - - it "loads tap aliases" do - expect(CoreTap.instance.alias_table).to eq({ - "fennel-lang" => "fennel", - "ponyc-lang" => "ponyc", - }) - end - - it "loads formula renames" do - expect(CoreTap.instance.formula_renames).to eq({ - "advancemenu" => "advancemame", - "amtk" => "libgedit-amtk", - "annie" => "lux", - "antlr2" => "antlr@2", - "romanesco" => "fennel", - }) - end - - it "loads tap migrations" do - expect(CoreTap.instance.tap_migrations).to eq({ - "adobe-air-sdk" => "homebrew/cask", - "android-ndk" => "homebrew/cask", - "android-platform-tools" => "homebrew/cask", - "android-sdk" => "homebrew/cask", - "app-engine-go-32" => "homebrew/cask/google-cloud-sdk", - }) - end - - it "loads tap git head" do - expect(Homebrew::API::Formula.tap_git_head) - .to eq(tap_git_head) - end - - context "when loading formulae" do - let(:fennel_metadata) do - { - "dependencies" => ["lua"], - "desc" => "Lua Lisp Language", - "full_name" => "fennel", - "homepage" => "https://fennel-lang.org", - "license" => "MIT", - "name" => "fennel", - "ruby_source_path" => "Formula/f/fennel.rb", - "tap" => "homebrew/core", - "tap_git_head" => tap_git_head, - "versions" => { "bottle"=>true, "head"=>nil, "stable"=>"1.4.0" }, - "ruby_source_checksum" => { - "sha256" => "5856e655fd1cea11496d67bc27fb14fee5cfbdea63c697c3773c7f247581197d", - }, - } - end - - let(:ponyc_metadata) do - { - "desc" => "Object-oriented, actor-model, capabilities-secure programming language", - "full_name" => "ponyc", - "homepage" => "https://www.ponylang.io/", - "license" => "BSD-2-Clause", - "name" => "ponyc", - "ruby_source_path" => "Formula/p/ponyc.rb", - "tap" => "homebrew/core", - "tap_git_head" => tap_git_head, - "uses_from_macos" => [{ "llvm"=>[:build, :test] }, "zlib"], - "uses_from_macos_bounds" => [{}, {}], - "versions" => { "bottle"=>true, "head"=>nil, "stable"=>"0.58.1" }, - "ruby_source_checksum" => { - "sha256" => "81d51c25d18710191beb62f9f380bae3d878aad815a65ec1ee2a3b132c1fadb3", - }, - } - end - - let(:inko_metadata) do - { - "desc" => "Safe and concurrent object-oriented programming language", - "full_name" => "inko", - "homepage" => "https://inko-lang.org/", - "license" => "MPL-2.0", - "name" => "inko", - "ruby_source_path" => "Formula/i/inko.rb", - "tap" => "homebrew/core", - "tap_git_head" => tap_git_head, - "dependencies" => ["llvm@15", "zstd"], - "uses_from_macos" => ["libffi", "ruby"], - "uses_from_macos_bounds" => [{ since: :catalina }, { since: :sierra }], - "versions" => { "bottle"=>true, "head"=>"HEAD", "stable"=>"0.14.0" }, - "ruby_source_checksum" => { - "sha256" => "843f6b5652483b971c83876201d68c95d5f32e67e55a75ac7c95d68c4350aa1c", - }, - } - end - - it "loads fennel" do - fennel = Formulary.factory("fennel") - expect(fennel.to_hash).to include(**fennel_metadata) - end - - it "loads fennel from rename" do - fennel = Formulary.factory("romanesco") - expect(fennel.to_hash).to include(**fennel_metadata) - end - - it "loads fennel from alias" do - fennel = Formulary.factory("fennel-lang") - expect(fennel.to_hash).to include(**fennel_metadata) - end - - it "loads ponyc" do - ponyc = Formulary.factory("ponyc") - expect(ponyc.to_hash).to include(**ponyc_metadata) - end - - it "loads ponyc from alias" do - ponyc = Formulary.factory("ponyc-lang") - expect(ponyc.to_hash).to include(**ponyc_metadata) - end - - it "loads ink" do - inko = Formulary.factory("inko") - expect(inko.to_hash).to include(**inko_metadata) - end - end - end -end diff --git a/Library/Homebrew/test/cask/cask_spec.rb b/Library/Homebrew/test/cask/cask_spec.rb index 038316a682..30d2059f96 100644 --- a/Library/Homebrew/test/cask/cask_spec.rb +++ b/Library/Homebrew/test/cask/cask_spec.rb @@ -236,23 +236,6 @@ RSpec.describe Cask::Cask, :cask do expect(cask.artifacts_list).to eq(expected_artifacts) end - it "skips flight blocks when compact is true" do - expected_artifacts = [ - { uninstall: [{ - rmdir: "#{TEST_TMPDIR}/empty_directory_path", - trash: ["#{TEST_TMPDIR}/foo", "#{TEST_TMPDIR}/bar"], - }] }, - { pkg: ["ManyArtifacts/ManyArtifacts.pkg"] }, - { app: ["ManyArtifacts/ManyArtifacts.app"] }, - { zap: [{ - rmdir: ["~/Library/Caches/ManyArtifacts", "~/Library/Application Support/ManyArtifacts"], - trash: "~/Library/Logs/ManyArtifacts.log", - }] }, - ] - - expect(cask.artifacts_list(compact: true)).to eq(expected_artifacts) - end - it "returns only uninstall artifacts when uninstall_only is true" do expected_artifacts = [ { uninstall_preflight: nil }, @@ -270,22 +253,6 @@ RSpec.describe Cask::Cask, :cask do expect(cask.artifacts_list(uninstall_only: true)).to eq(expected_artifacts) end - - it "skips flight blocks and returns only uninstall artifacts when compact and uninstall_only are true" do - expected_artifacts = [ - { uninstall: [{ - rmdir: "#{TEST_TMPDIR}/empty_directory_path", - trash: ["#{TEST_TMPDIR}/foo", "#{TEST_TMPDIR}/bar"], - }] }, - { app: ["ManyArtifacts/ManyArtifacts.app"] }, - { zap: [{ - rmdir: ["~/Library/Caches/ManyArtifacts", "~/Library/Application Support/ManyArtifacts"], - trash: "~/Library/Logs/ManyArtifacts.log", - }] }, - ] - - expect(cask.artifacts_list(compact: true, uninstall_only: true)).to eq(expected_artifacts) - end end describe "#uninstall_flight_blocks?" do diff --git a/Library/Homebrew/test/cmd/alias_spec.rb b/Library/Homebrew/test/cmd/alias_spec.rb new file mode 100644 index 0000000000..35442f0ad8 --- /dev/null +++ b/Library/Homebrew/test/cmd/alias_spec.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +require "cmd/alias" +require "cmd/shared_examples/args_parse" + +RSpec.describe Homebrew::Cmd::Alias do + it_behaves_like "parseable arguments" + + it "sets an alias", :integration_test do + expect { brew "alias", "foo=bar" } + .to not_to_output.to_stdout + .and not_to_output.to_stderr + .and be_a_success + expect { brew "alias" } + .to output(/brew alias foo='bar'/).to_stdout + .and not_to_output.to_stderr + .and be_a_success + end +end diff --git a/Library/Homebrew/test/cmd/unalias_spec.rb b/Library/Homebrew/test/cmd/unalias_spec.rb new file mode 100644 index 0000000000..4a9d799404 --- /dev/null +++ b/Library/Homebrew/test/cmd/unalias_spec.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +require "cmd/unalias" +require "cmd/shared_examples/args_parse" + +RSpec.describe Homebrew::Cmd::Unalias do + it_behaves_like "parseable arguments" + + it "unsets an alias", :integration_test do + expect { brew "alias", "foo=bar" } + .to not_to_output.to_stdout + .and not_to_output.to_stderr + .and be_a_success + expect { brew "alias" } + .to output(/brew alias foo='bar'/).to_stdout + .and not_to_output.to_stderr + .and be_a_success + expect { brew "unalias", "foo" } + .to not_to_output.to_stdout + .and not_to_output.to_stderr + .and be_a_success + expect { brew "alias" } + .to not_to_output.to_stdout + .and not_to_output.to_stderr + .and be_a_success + end +end diff --git a/Library/Homebrew/test/livecheck/strategy_spec.rb b/Library/Homebrew/test/livecheck/strategy_spec.rb index 7ed67125bd..ddb3bde2f5 100644 --- a/Library/Homebrew/test/livecheck/strategy_spec.rb +++ b/Library/Homebrew/test/livecheck/strategy_spec.rb @@ -5,29 +5,257 @@ require "livecheck/strategy" RSpec.describe Homebrew::Livecheck::Strategy do subject(:strategy) { described_class } + let(:url) { "https://brew.sh/" } + let(:redirection_url) { "https://brew.sh/redirection" } + + let(:post_hash) do + { + "empty" => "", + "boolean" => "true", + "number" => "1", + "string" => "a + b = c", + } + end + let(:post_hash_symbol_keys) do + { + empty: "", + boolean: "true", + number: "1", + string: "a + b = c", + } + end + let(:form_string) { "empty=&boolean=true&number=1&string=a+%2B+b+%3D+c" } + let(:json_string) { '{"empty":"","boolean":"true","number":"1","string":"a + b = c"}' } + + let(:response_hash) do + response_hash = {} + + response_hash[:ok] = { + status_code: "200", + status_text: "OK", + headers: { + "cache-control" => "max-age=604800", + "content-type" => "text/html; charset=UTF-8", + "date" => "Wed, 1 Jan 2020 01:23:45 GMT", + "expires" => "Wed, 31 Jan 2020 01:23:45 GMT", + "last-modified" => "Thu, 1 Jan 2019 01:23:45 GMT", + "content-length" => "123", + }, + } + + response_hash[:redirection] = { + status_code: "301", + status_text: "Moved Permanently", + headers: { + "cache-control" => "max-age=604800", + "content-type" => "text/html; charset=UTF-8", + "date" => "Wed, 1 Jan 2020 01:23:45 GMT", + "expires" => "Wed, 31 Jan 2020 01:23:45 GMT", + "last-modified" => "Thu, 1 Jan 2019 01:23:45 GMT", + "content-length" => "123", + "location" => redirection_url, + }, + } + + response_hash + end + + let(:body) do + <<~HTML + + + + + Thank you! + + +

Download

+

This download link could have been made publicly available in a reasonable fashion but we appreciate that you jumped through the hoops that we carefully set up!: Example v1.2.3

+

The current legacy version is: Example v0.1.2

+ + + HTML + end + + let(:response_text) do + response_text = {} + + response_text[:ok] = <<~EOS + HTTP/1.1 #{response_hash[:ok][:status_code]} #{response_hash[:ok][:status_text]}\r + Cache-Control: #{response_hash[:ok][:headers]["cache-control"]}\r + Content-Type: #{response_hash[:ok][:headers]["content-type"]}\r + Date: #{response_hash[:ok][:headers]["date"]}\r + Expires: #{response_hash[:ok][:headers]["expires"]}\r + Last-Modified: #{response_hash[:ok][:headers]["last-modified"]}\r + Content-Length: #{response_hash[:ok][:headers]["content-length"]}\r + \r + #{body.rstrip} + EOS + + response_text[:redirection_to_ok] = response_text[:ok].sub( + "HTTP/1.1 #{response_hash[:ok][:status_code]} #{response_hash[:ok][:status_text]}\r", + "HTTP/1.1 #{response_hash[:redirection][:status_code]} #{response_hash[:redirection][:status_text]}\r\n" \ + "Location: #{response_hash[:redirection][:headers]["location"]}\r", + ) + + response_text + end + describe "::from_symbol" do it "returns the Strategy module represented by the Symbol argument" do expect(strategy.from_symbol(:page_match)).to eq(Homebrew::Livecheck::Strategy::PageMatch) end + + it "returns `nil` if the argument is `nil`" do + expect(strategy.from_symbol(nil)).to be_nil + end end describe "::from_url" do - let(:url) { "https://sourceforge.net/projects/test" } + let(:sourceforge_url) { "https://sourceforge.net/projects/test" } - context "when no regex is provided" do + context "when a regex or `strategy` block is not provided" do it "returns an array of usable strategies which doesn't include PageMatch" do - expect(strategy.from_url(url)).to eq([Homebrew::Livecheck::Strategy::Sourceforge]) + expect(strategy.from_url(sourceforge_url)).to eq([Homebrew::Livecheck::Strategy::Sourceforge]) end end - context "when a regex is provided" do + context "when a regex or `strategy` block is provided" do it "returns an array of usable strategies including PageMatch, sorted in descending order by priority" do - expect(strategy.from_url(url, regex_provided: true)) + expect(strategy.from_url(sourceforge_url, regex_provided: true)) .to eq( [Homebrew::Livecheck::Strategy::Sourceforge, Homebrew::Livecheck::Strategy::PageMatch], ) end end + + context "when a `strategy` block is required and one is provided" do + it "returns an array of usable strategies including the specified strategy" do + # The strategies array is naturally in alphabetic order when all + # applicable strategies have the same priority + expect(strategy.from_url(url, livecheck_strategy: :json, block_provided: true)) + .to eq([Homebrew::Livecheck::Strategy::Json, Homebrew::Livecheck::Strategy::PageMatch]) + expect(strategy.from_url(url, livecheck_strategy: :xml, block_provided: true)) + .to eq([Homebrew::Livecheck::Strategy::PageMatch, Homebrew::Livecheck::Strategy::Xml]) + expect(strategy.from_url(url, livecheck_strategy: :yaml, block_provided: true)) + .to eq([Homebrew::Livecheck::Strategy::PageMatch, Homebrew::Livecheck::Strategy::Yaml]) + end + end + + context "when a `strategy` block is required and one is not provided" do + it "returns an array of usable strategies not including the specified strategy" do + expect(strategy.from_url(url, livecheck_strategy: :json, block_provided: false)).to eq([]) + expect(strategy.from_url(url, livecheck_strategy: :xml, block_provided: false)).to eq([]) + expect(strategy.from_url(url, livecheck_strategy: :yaml, block_provided: false)).to eq([]) + end + end + end + + describe "::post_args" do + it "returns an array including `--data` and an encoded form data string" do + expect(strategy.post_args(post_form: post_hash)).to eq(["--data", form_string]) + expect(strategy.post_args(post_form: post_hash_symbol_keys)).to eq(["--data", form_string]) + + # If both `post_form` and `post_json` are present, only `post_form` will + # be used. + expect(strategy.post_args(post_form: post_hash, post_json: post_hash)).to eq(["--data", form_string]) + end + + it "returns an array including `--json` and a JSON string" do + expect(strategy.post_args(post_json: post_hash)).to eq(["--json", json_string]) + expect(strategy.post_args(post_json: post_hash_symbol_keys)).to eq(["--json", json_string]) + end + + it "returns an empty array if `post_form` value is blank" do + expect(strategy.post_args(post_form: {})).to eq([]) + end + + it "returns an empty array if `post_json` value is blank" do + expect(strategy.post_args(post_json: {})).to eq([]) + end + + it "returns an empty array if hash argument doesn't have a `post_form` or `post_json` value" do + expect(strategy.post_args).to eq([]) + end + end + + describe "::page_headers" do + let(:responses) { [response_hash[:ok]] } + + it "returns headers from fetched content" do + allow(strategy).to receive(:curl_headers).and_return({ responses:, body: }) + + expect(strategy.page_headers(url)).to eq([responses.first[:headers]]) + end + + it "handles `post_form` `url` options" do + allow(strategy).to receive(:curl_headers).and_return({ responses:, body: }) + + expect(strategy.page_headers(url, url_options: { post_form: post_hash })) + .to eq([responses.first[:headers]]) + end + + it "returns an empty array if `curl_headers` only raises an `ErrorDuringExecution` error" do + allow(strategy).to receive(:curl_headers).and_raise(ErrorDuringExecution.new([], status: 1)) + + expect(strategy.page_headers(url)).to eq([]) + end + end + + describe "::page_content" do + let(:curl_version) { Version.new("8.7.1") } + let(:success_status) { instance_double(Process::Status, success?: true, exitstatus: 0) } + + it "returns hash including fetched content" do + allow_any_instance_of(Utils::Curl).to receive(:curl_version).and_return(curl_version) + allow(strategy).to receive(:curl_output).and_return([response_text[:ok], nil, success_status]) + + expect(strategy.page_content(url)).to eq({ content: body }) + end + + it "handles `post_form` `url` option" do + allow_any_instance_of(Utils::Curl).to receive(:curl_version).and_return(curl_version) + allow(strategy).to receive(:curl_output).and_return([response_text[:ok], nil, success_status]) + + expect(strategy.page_content(url, url_options: { post_form: post_hash })).to eq({ content: body }) + end + + it "handles `post_json` `url` option" do + allow_any_instance_of(Utils::Curl).to receive(:curl_version).and_return(curl_version) + allow(strategy).to receive(:curl_output).and_return([response_text[:ok], nil, success_status]) + + expect(strategy.page_content(url, url_options: { post_json: post_hash })).to eq({ content: body }) + end + + it "returns error `messages` from `stderr` in the return hash on failure when `stderr` is not `nil`" do + error_message = "curl: (6) Could not resolve host: brew.sh" + allow_any_instance_of(Utils::Curl).to receive(:curl_version).and_return(curl_version) + allow(strategy).to receive(:curl_output).and_return([ + nil, + error_message, + instance_double(Process::Status, success?: false, exitstatus: 6), + ]) + + expect(strategy.page_content(url)).to eq({ messages: [error_message] }) + end + + it "returns default error `messages` in the return hash on failure when `stderr` is `nil`" do + allow_any_instance_of(Utils::Curl).to receive(:curl_version).and_return(curl_version) + allow(strategy).to receive(:curl_output).and_return([ + nil, + nil, + instance_double(Process::Status, success?: false, exitstatus: 1), + ]) + + expect(strategy.page_content(url)).to eq({ messages: ["cURL failed without a detectable error"] }) + end + + it "returns hash including `final_url` if it differs from initial `url`" do + allow_any_instance_of(Utils::Curl).to receive(:curl_version).and_return(curl_version) + allow(strategy).to receive(:curl_output).and_return([response_text[:redirection_to_ok], nil, success_status]) + + expect(strategy.page_content(url)).to eq({ content: body, final_url: redirection_url }) + end end describe "::handle_block_return" do diff --git a/Library/Homebrew/test/livecheck_spec.rb b/Library/Homebrew/test/livecheck_spec.rb index 51f0e1c929..d213d227d6 100644 --- a/Library/Homebrew/test/livecheck_spec.rb +++ b/Library/Homebrew/test/livecheck_spec.rb @@ -27,6 +27,15 @@ RSpec.describe Livecheck do end let(:livecheck_c) { described_class.new(c) } + let(:post_hash) do + { + "empty" => "", + "boolean" => "true", + "number" => "1", + "string" => "a + b = c", + } + end + describe "#formula" do it "returns nil if not set" do expect(livecheck_f.formula).to be_nil @@ -90,13 +99,23 @@ RSpec.describe Livecheck do end describe "#strategy" do + block = proc { |page, regex| page.scan(regex).map { |match| match[0].tr("_", ".") } } + it "returns nil if not set" do expect(livecheck_f.strategy).to be_nil + expect(livecheck_f.strategy_block).to be_nil end it "returns the Symbol if set" do livecheck_f.strategy(:page_match) expect(livecheck_f.strategy).to eq(:page_match) + expect(livecheck_f.strategy_block).to be_nil + end + + it "sets `strategy_block` when provided" do + livecheck_f.strategy(:page_match, &block) + expect(livecheck_f.strategy).to eq(:page_match) + expect(livecheck_f.strategy_block).to eq(block) end end @@ -137,25 +156,38 @@ RSpec.describe Livecheck do expect(livecheck_c.url).to eq(:url) end + it "sets `url_options` when provided" do + post_args = { post_form: post_hash } + livecheck_f.url(url_string, **post_args) + expect(livecheck_f.url_options).to eq(post_args) + end + it "raises an ArgumentError if the argument isn't a valid Symbol" do expect do livecheck_f.url(:not_a_valid_symbol) end.to raise_error ArgumentError end + + it "raises an ArgumentError if both `post_form` and `post_json` arguments are provided" do + expect do + livecheck_f.url(:stable, post_form: post_hash, post_json: post_hash) + end.to raise_error ArgumentError + end end describe "#to_hash" do it "returns a Hash of all instance variables" do expect(livecheck_f.to_hash).to eq( { - "cask" => nil, - "formula" => nil, - "regex" => nil, - "skip" => false, - "skip_msg" => nil, - "strategy" => nil, - "throttle" => nil, - "url" => nil, + "cask" => nil, + "formula" => nil, + "regex" => nil, + "skip" => false, + "skip_msg" => nil, + "strategy" => nil, + "throttle" => nil, + "url" => nil, + "url_options" => nil, }, ) end diff --git a/Library/Homebrew/test/spec_helper.rb b/Library/Homebrew/test/spec_helper.rb index 5d521a4f44..92c2135b5a 100644 --- a/Library/Homebrew/test/spec_helper.rb +++ b/Library/Homebrew/test/spec_helper.rb @@ -63,6 +63,7 @@ TEST_DIRECTORIES = [ HOMEBREW_LOCKS, HOMEBREW_LOGS, HOMEBREW_TEMP, + HOMEBREW_ALIASES, ].freeze # Make `instance_double` and `class_double` diff --git a/Library/Homebrew/test/support/fixtures/internal_tap_json/homebrew-core.json b/Library/Homebrew/test/support/fixtures/internal_tap_json/homebrew-core.json deleted file mode 100644 index b704184417..0000000000 --- a/Library/Homebrew/test/support/fixtures/internal_tap_json/homebrew-core.json +++ /dev/null @@ -1,218 +0,0 @@ -{ - "tap_git_head": "9977471165641744a829d3e494fa563407503297", - "aliases": { - "fennel-lang": "fennel", - "ponyc-lang": "ponyc" - }, - "renames": { - "advancemenu": "advancemame", - "amtk": "libgedit-amtk", - "annie": "lux", - "antlr2": "antlr@2", - "romanesco": "fennel" - }, - "tap_migrations": { - "adobe-air-sdk": "homebrew/cask", - "android-ndk": "homebrew/cask", - "android-platform-tools": "homebrew/cask", - "android-sdk": "homebrew/cask", - "app-engine-go-32": "homebrew/cask/google-cloud-sdk" - }, - "formulae": { - "fennel": { - "desc": "Lua Lisp Language", - "license": "MIT", - "homepage": "https://fennel-lang.org", - "urls": { - "stable": { - "url": "https://github.com/bakpakin/Fennel/archive/refs/tags/1.4.0.tar.gz", - "checksum": "161eb7f17f86e95de09070214d042fb25372f71ad266f451431f3109e87965c7" - } - }, - "post_install_defined": false, - "ruby_source_path": "Formula/f/fennel.rb", - "ruby_source_sha256": "5856e655fd1cea11496d67bc27fb14fee5cfbdea63c697c3773c7f247581197d", - "version": "1.4.0", - "bottle": { - "files": { - "all": { - "cellar": ":any_skip_relocation", - "sha256": "f46028597883cbc38864c61bd3fa402da9cb90ce97415d51a7b5279bc17f7bd0" - } - } - }, - "dependencies": { - "lua": null - } - }, - "inko": { - "desc": "Safe and concurrent object-oriented programming language", - "license": "MPL-2.0", - "homepage": "https://inko-lang.org/", - "urls": { - "stable": { - "url": "https://releases.inko-lang.org/0.14.0.tar.gz", - "checksum": "4e2c82911d6026f76c42ccc164dc45b1b5e331db2e9557460d9319d682668e65" - }, - "head": { - "url": "https://github.com/inko-lang/inko.git", - "branch": "main" - } - }, - "post_install_defined": false, - "ruby_source_path": "Formula/i/inko.rb", - "ruby_source_sha256": "843f6b5652483b971c83876201d68c95d5f32e67e55a75ac7c95d68c4350aa1c", - "version": "0.14.0", - "bottle": { - "files": { - "arm64_sonoma": { - "cellar": ":any", - "sha256": "f6ff66fdfb3aac69263c32a8a29d13e9d28a80ae33807f34460e55d8c1b228c6" - }, - "arm64_ventura": { - "cellar": ":any", - "sha256": "be59d916d29d85bb8bc4474eb1c7d42a56236835c3c21b0e36fb9e9df0a25e6e" - }, - "arm64_monterey": { - "cellar": ":any", - "sha256": "9522c1f89b997dedaa3167ce4dbfa4a2d8c660acddecd32a99a515922e851b52" - }, - "sonoma": { - "cellar": ":any", - "sha256": "8e32d823ce9712ae2d5a2b9cbe0c9b727223098b3e66b003da087032be9f6818" - }, - "ventura": { - "cellar": ":any", - "sha256": "178865db1e2b60b4085a2465e8a3879794030a6d22c99b58c95e4bdf5418ef1b" - }, - "monterey": { - "cellar": ":any", - "sha256": "6ef924939c42d7bb2ec4e0d65cf293147a593f829619928d2580b419ec19b26c" - }, - "x86_64_linux": { - "cellar": ":any_skip_relocation", - "sha256": "14a02c119990d6a17062290439ac74e6667b41dcb90b18cd90b36d2a09715e10" - } - } - }, - "dependencies": { - "coreutils": { - "tags": [ - "build" - ] - }, - "rust": { - "tags": [ - "build" - ] - }, - "llvm@15": null, - "zstd": null, - "libffi": { - "uses_from_macos": { - "since": "catalina" - } - }, - "ruby": { - "uses_from_macos": { - "since": "sierra" - } - } - }, - "head_dependencies": { - "coreutils": { - "tags": [ - "build" - ] - }, - "rust": { - "tags": [ - "build" - ] - }, - "llvm@15": null, - "zstd": null, - "libffi": { - "uses_from_macos": { - "since": "catalina" - } - }, - "ruby": { - "uses_from_macos": { - "since": "sierra" - } - } - } - }, - "ponyc": { - "desc": "Object-oriented, actor-model, capabilities-secure programming language", - "license": "BSD-2-Clause", - "homepage": "https://www.ponylang.io/", - "urls": { - "stable": { - "url": "https://github.com/ponylang/ponyc.git", - "tag": "0.58.1", - "revision": "fe3895eb4af494bf36d7690641bdfb5755db8350" - } - }, - "post_install_defined": false, - "ruby_source_path": "Formula/p/ponyc.rb", - "ruby_source_sha256": "81d51c25d18710191beb62f9f380bae3d878aad815a65ec1ee2a3b132c1fadb3", - "version": "0.58.1", - "bottle": { - "files": { - "arm64_sonoma": { - "cellar": ":any_skip_relocation", - "sha256": "e3aecfcf02aea56d53d82691e2ad7a780f771023d7070271bfce96b17439a34d" - }, - "arm64_ventura": { - "cellar": ":any_skip_relocation", - "sha256": "6ff83717191e16e4f852fb3be8f838afba312cc39e601bb5cebd2a618a328658" - }, - "arm64_monterey": { - "cellar": ":any_skip_relocation", - "sha256": "25c91bce200583a96f4cea34f31393c8f10eadcab363cc7d4d864d15f5f97e25" - }, - "sonoma": { - "cellar": ":any_skip_relocation", - "sha256": "5f4c550ce33e2970e0ada18a409755fa62936181289a21c15582ff80343866b6" - }, - "ventura": { - "cellar": ":any_skip_relocation", - "sha256": "f26c799f45013685da779bf2008ebe1907f9b3a93d5f260ce271a3f3b628da50" - }, - "monterey": { - "cellar": ":any_skip_relocation", - "sha256": "1cff10d068b36b18b253d235424c4f5aef71ff9ee44f2522c4b041dd4383ec30" - }, - "x86_64_linux": { - "cellar": ":any_skip_relocation", - "sha256": "ab49318d75eed3ee932c8e5add22f252ec0c852aad94945022877f926e93899f" - } - } - }, - "dependencies": { - "cmake": { - "tags": [ - "build" - ] - }, - "python@3.12": { - "tags": [ - "build" - ] - }, - "llvm": { - "tags": [ - "build", - "test" - ], - "uses_from_macos": null - }, - "zlib": { - "uses_from_macos": null - } - } - } - } -} diff --git a/Library/Homebrew/test/support/fixtures/internal_tap_json/homebrew-core/Formula/f/fennel.rb b/Library/Homebrew/test/support/fixtures/internal_tap_json/homebrew-core/Formula/f/fennel.rb deleted file mode 100644 index c98a3f26c2..0000000000 --- a/Library/Homebrew/test/support/fixtures/internal_tap_json/homebrew-core/Formula/f/fennel.rb +++ /dev/null @@ -1,25 +0,0 @@ -class Fennel < Formula - desc "Lua Lisp Language" - homepage "https://fennel-lang.org" - url "https://github.com/bakpakin/Fennel/archive/refs/tags/1.4.0.tar.gz" - sha256 "161eb7f17f86e95de09070214d042fb25372f71ad266f451431f3109e87965c7" - license "MIT" - - bottle do - sha256 cellar: :any_skip_relocation, all: "f46028597883cbc38864c61bd3fa402da9cb90ce97415d51a7b5279bc17f7bd0" - end - - depends_on "lua" - - def install - system "make" - bin.install "fennel" - - lua = Formula["lua"] - (share/"lua"/lua.version.major_minor).install "fennel.lua" - end - - test do - assert_match "hello, world!", shell_output("#{bin}/fennel -e '(print \"hello, world!\")'") - end -end diff --git a/Library/Homebrew/test/support/fixtures/internal_tap_json/homebrew-core/Formula/i/inko.rb b/Library/Homebrew/test/support/fixtures/internal_tap_json/homebrew-core/Formula/i/inko.rb deleted file mode 100644 index 9f56b2d0f4..0000000000 --- a/Library/Homebrew/test/support/fixtures/internal_tap_json/homebrew-core/Formula/i/inko.rb +++ /dev/null @@ -1,45 +0,0 @@ -class Inko < Formula - desc "Safe and concurrent object-oriented programming language" - homepage "https://inko-lang.org/" - url "https://releases.inko-lang.org/0.14.0.tar.gz" - sha256 "4e2c82911d6026f76c42ccc164dc45b1b5e331db2e9557460d9319d682668e65" - license "MPL-2.0" - head "https://github.com/inko-lang/inko.git", branch: "main" - - bottle do - sha256 cellar: :any, arm64_sonoma: "f6ff66fdfb3aac69263c32a8a29d13e9d28a80ae33807f34460e55d8c1b228c6" - sha256 cellar: :any, arm64_ventura: "be59d916d29d85bb8bc4474eb1c7d42a56236835c3c21b0e36fb9e9df0a25e6e" - sha256 cellar: :any, arm64_monterey: "9522c1f89b997dedaa3167ce4dbfa4a2d8c660acddecd32a99a515922e851b52" - sha256 cellar: :any, sonoma: "8e32d823ce9712ae2d5a2b9cbe0c9b727223098b3e66b003da087032be9f6818" - sha256 cellar: :any, ventura: "178865db1e2b60b4085a2465e8a3879794030a6d22c99b58c95e4bdf5418ef1b" - sha256 cellar: :any, monterey: "6ef924939c42d7bb2ec4e0d65cf293147a593f829619928d2580b419ec19b26c" - sha256 cellar: :any_skip_relocation, x86_64_linux: "14a02c119990d6a17062290439ac74e6667b41dcb90b18cd90b36d2a09715e10" - end - - depends_on "coreutils" => :build - depends_on "rust" => :build - depends_on "llvm@15" - depends_on "zstd" - - uses_from_macos "libffi", since: :catalina - uses_from_macos "ruby", since: :sierra - - def install - ENV.prepend_path "PATH", Formula["coreutils"].opt_libexec/"gnubin" - system "make", "build", "PREFIX=#{prefix}" - system "make", "install", "PREFIX=#{prefix}" - end - - test do - (testpath/"hello.inko").write <<~EOS - import std.stdio.STDOUT - - class async Main { - fn async main { - STDOUT.new.print('Hello, world!') - } - } - EOS - assert_equal "Hello, world!\n", shell_output("#{bin}/inko run hello.inko") - end -end diff --git a/Library/Homebrew/test/support/fixtures/internal_tap_json/homebrew-core/Formula/p/ponyc.rb b/Library/Homebrew/test/support/fixtures/internal_tap_json/homebrew-core/Formula/p/ponyc.rb deleted file mode 100644 index 63ad8bdfab..0000000000 --- a/Library/Homebrew/test/support/fixtures/internal_tap_json/homebrew-core/Formula/p/ponyc.rb +++ /dev/null @@ -1,60 +0,0 @@ -class Ponyc < Formula - desc "Object-oriented, actor-model, capabilities-secure programming language" - homepage "https://www.ponylang.io/" - url "https://github.com/ponylang/ponyc.git", - tag: "0.58.1", - revision: "fe3895eb4af494bf36d7690641bdfb5755db8350" - license "BSD-2-Clause" - - bottle do - sha256 cellar: :any_skip_relocation, arm64_sonoma: "e3aecfcf02aea56d53d82691e2ad7a780f771023d7070271bfce96b17439a34d" - sha256 cellar: :any_skip_relocation, arm64_ventura: "6ff83717191e16e4f852fb3be8f838afba312cc39e601bb5cebd2a618a328658" - sha256 cellar: :any_skip_relocation, arm64_monterey: "25c91bce200583a96f4cea34f31393c8f10eadcab363cc7d4d864d15f5f97e25" - sha256 cellar: :any_skip_relocation, sonoma: "5f4c550ce33e2970e0ada18a409755fa62936181289a21c15582ff80343866b6" - sha256 cellar: :any_skip_relocation, ventura: "f26c799f45013685da779bf2008ebe1907f9b3a93d5f260ce271a3f3b628da50" - sha256 cellar: :any_skip_relocation, monterey: "1cff10d068b36b18b253d235424c4f5aef71ff9ee44f2522c4b041dd4383ec30" - sha256 cellar: :any_skip_relocation, x86_64_linux: "ab49318d75eed3ee932c8e5add22f252ec0c852aad94945022877f926e93899f" - end - - depends_on "cmake" => :build - depends_on "python@3.12" => :build - - uses_from_macos "llvm" => [:build, :test] - uses_from_macos "zlib" - - # We use LLVM to work around an error while building bundled `google-benchmark` with GCC - fails_with :gcc do - cause <<-EOS - .../src/gbenchmark/src/thread_manager.h:50:31: error: expected ')' before '(' token - 50 | GUARDED_BY(GetBenchmarkMutex()) Result results; - | ^ - EOS - end - - def install - inreplace "CMakeLists.txt", "PONY_COMPILER=\"${CMAKE_C_COMPILER}\"", "PONY_COMPILER=\"#{ENV.cc}\"" if OS.linux? - - ENV["CMAKE_FLAGS"] = "-DCMAKE_OSX_SYSROOT=#{MacOS.sdk_path}" if OS.mac? - ENV["MAKEFLAGS"] = "build_flags=-j#{ENV.make_jobs}" - - system "make", "libs" - system "make", "configure" - system "make", "build" - system "make", "install", "DESTDIR=#{prefix}" - end - - test do - # ENV["CC"] returns llvm_clang, which does not work in a test block. - ENV.clang - - system "#{bin}/ponyc", "-rexpr", "#{prefix}/packages/stdlib" - - (testpath/"test/main.pony").write <<~EOS - actor Main - new create(env: Env) => - env.out.print("Hello World!") - EOS - system "#{bin}/ponyc", "test" - assert_equal "Hello World!", shell_output("./test1").strip - end -end diff --git a/Library/Homebrew/test/support/fixtures/internal_tap_json/homebrew-core/formula_renames.json b/Library/Homebrew/test/support/fixtures/internal_tap_json/homebrew-core/formula_renames.json deleted file mode 100644 index ff923dc297..0000000000 --- a/Library/Homebrew/test/support/fixtures/internal_tap_json/homebrew-core/formula_renames.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "advancemenu": "advancemame", - "amtk": "libgedit-amtk", - "annie": "lux", - "antlr2": "antlr@2", - "romanesco": "fennel" -} diff --git a/Library/Homebrew/test/support/fixtures/internal_tap_json/homebrew-core/tap_migrations.json b/Library/Homebrew/test/support/fixtures/internal_tap_json/homebrew-core/tap_migrations.json deleted file mode 100644 index 2bdd3fd5c6..0000000000 --- a/Library/Homebrew/test/support/fixtures/internal_tap_json/homebrew-core/tap_migrations.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "adobe-air-sdk": "homebrew/cask", - "android-ndk": "homebrew/cask", - "android-platform-tools": "homebrew/cask", - "android-sdk": "homebrew/cask", - "app-engine-go-32": "homebrew/cask/google-cloud-sdk" -} diff --git a/Library/Homebrew/test/support/lib/startup/config.rb b/Library/Homebrew/test/support/lib/startup/config.rb index 796ca1ea62..e2cf07ff7e 100644 --- a/Library/Homebrew/test/support/lib/startup/config.rb +++ b/Library/Homebrew/test/support/lib/startup/config.rb @@ -24,6 +24,7 @@ HOMEBREW_DATA_PATH = (HOMEBREW_LIBRARY_PATH/"data").freeze # Paths redirected to a temporary directory and wiped at the end of the test run HOMEBREW_PREFIX = (Pathname(TEST_TMPDIR)/"prefix").freeze +HOMEBREW_ALIASES = (Pathname(TEST_TMPDIR)/"aliases").freeze HOMEBREW_REPOSITORY = HOMEBREW_PREFIX.dup.freeze HOMEBREW_LIBRARY = (HOMEBREW_REPOSITORY/"Library").freeze HOMEBREW_CACHE = (HOMEBREW_PREFIX.parent/"cache").freeze diff --git a/completions/bash/brew b/completions/bash/brew index 278a38f692..173f29f49c 100644 --- a/completions/bash/brew +++ b/completions/bash/brew @@ -381,6 +381,23 @@ _brew_abv() { __brew_complete_casks } +_brew_alias() { + local cur="${COMP_WORDS[COMP_CWORD]}" + case "${cur}" in + -*) + __brewcomp " + --debug + --edit + --help + --quiet + --verbose + " + return + ;; + *) ;; + esac +} + _brew_analytics() { local cur="${COMP_WORDS[COMP_CWORD]}" case "${cur}" in @@ -2473,6 +2490,22 @@ _brew_typecheck() { __brew_complete_tapped } +_brew_unalias() { + local cur="${COMP_WORDS[COMP_CWORD]}" + case "${cur}" in + -*) + __brewcomp " + --debug + --help + --quiet + --verbose + " + return + ;; + *) ;; + esac +} + _brew_unbottled() { local cur="${COMP_WORDS[COMP_CWORD]}" case "${cur}" in @@ -2963,6 +2996,7 @@ _brew() { -S) _brew__s ;; -v) _brew__v ;; abv) _brew_abv ;; + alias) _brew_alias ;; analytics) _brew_analytics ;; audit) _brew_audit ;; autoremove) _brew_autoremove ;; @@ -3053,6 +3087,7 @@ _brew() { test) _brew_test ;; tests) _brew_tests ;; typecheck) _brew_typecheck ;; + unalias) _brew_unalias ;; unbottled) _brew_unbottled ;; uninstal) _brew_uninstal ;; uninstall) _brew_uninstall ;; diff --git a/completions/fish/brew.fish b/completions/fish/brew.fish index 2ad245fcf8..ea6849ed4e 100644 --- a/completions/fish/brew.fish +++ b/completions/fish/brew.fish @@ -319,6 +319,14 @@ __fish_brew_complete_arg 'abv; and not __fish_seen_argument -l cask -l casks' -a __fish_brew_complete_arg 'abv; and not __fish_seen_argument -l formula -l formulae' -a '(__fish_brew_suggest_casks_all)' +__fish_brew_complete_cmd 'alias' 'Show existing aliases' +__fish_brew_complete_arg 'alias' -l debug -d 'Display any debugging information' +__fish_brew_complete_arg 'alias' -l edit -d 'Edit aliases in a text editor. Either one or all aliases may be opened at once. If the given alias doesn\'t exist it\'ll be pre-populated with a template' +__fish_brew_complete_arg 'alias' -l help -d 'Show this message' +__fish_brew_complete_arg 'alias' -l quiet -d 'Make some output more quiet' +__fish_brew_complete_arg 'alias' -l verbose -d 'Make some output more verbose' + + __fish_brew_complete_cmd 'analytics' 'Control Homebrew\'s anonymous aggregate user behaviour analytics' __fish_brew_complete_sub_cmd 'analytics' 'state' __fish_brew_complete_sub_cmd 'analytics' 'on' @@ -555,7 +563,7 @@ __fish_brew_complete_arg 'contributions' -l debug -d 'Display any debugging info __fish_brew_complete_arg 'contributions' -l from -d 'Date (ISO-8601 format) to start searching contributions. Omitting this flag searches the last year' __fish_brew_complete_arg 'contributions' -l help -d 'Show this message' __fish_brew_complete_arg 'contributions' -l quiet -d 'Make some output more quiet' -__fish_brew_complete_arg 'contributions' -l repositories -d 'Specify a comma-separated list of repositories to search. Supported repositories: `brew`, `core`, `cask`, `aliases`, `bundle`, `command-not-found`, `test-bot` and `services`. Omitting this flag, or specifying `--repositories=primary`, searches only the main repositories: brew,core,cask. Specifying `--repositories=all`, searches all repositories. ' +__fish_brew_complete_arg 'contributions' -l repositories -d 'Specify a comma-separated list of repositories to search. Supported repositories: `brew`, `core`, `cask`, `bundle`, `command-not-found`, `test-bot` and `services`. Omitting this flag, or specifying `--repositories=primary`, searches only the main repositories: brew,core,cask. Specifying `--repositories=all`, searches all repositories. ' __fish_brew_complete_arg 'contributions' -l to -d 'Date (ISO-8601 format) to stop searching contributions' __fish_brew_complete_arg 'contributions' -l user -d 'Specify a comma-separated list of GitHub usernames or email addresses to find contributions from. Omitting this flag searches maintainers' __fish_brew_complete_arg 'contributions' -l verbose -d 'Make some output more verbose' @@ -1603,6 +1611,13 @@ __fish_brew_complete_arg 'typecheck' -l verbose -d 'Make some output more verbos __fish_brew_complete_arg 'typecheck' -a '(__fish_brew_suggest_taps_installed)' +__fish_brew_complete_cmd 'unalias' 'Remove aliases' +__fish_brew_complete_arg 'unalias' -l debug -d 'Display any debugging information' +__fish_brew_complete_arg 'unalias' -l help -d 'Show this message' +__fish_brew_complete_arg 'unalias' -l quiet -d 'Make some output more quiet' +__fish_brew_complete_arg 'unalias' -l verbose -d 'Make some output more verbose' + + __fish_brew_complete_cmd 'unbottled' 'Show the unbottled dependents of formulae' __fish_brew_complete_arg 'unbottled' -l debug -d 'Display any debugging information' __fish_brew_complete_arg 'unbottled' -l dependents -d 'Skip getting analytics data and sort by number of dependents instead' diff --git a/completions/internal_commands_list.txt b/completions/internal_commands_list.txt index a4836ba0fe..b3162e50ae 100644 --- a/completions/internal_commands_list.txt +++ b/completions/internal_commands_list.txt @@ -10,6 +10,7 @@ -S -v abv +alias analytics audit autoremove @@ -100,6 +101,7 @@ tc test tests typecheck +unalias unbottled uninstall unlink diff --git a/completions/zsh/_brew b/completions/zsh/_brew index 0f3618a79d..03b75e2567 100644 --- a/completions/zsh/_brew +++ b/completions/zsh/_brew @@ -139,6 +139,7 @@ __brew_internal_commands() { '--prefix:Display Homebrew'\''s install path' '--repository:Display where Homebrew'\''s Git repository is located' '--version:Print the version numbers of Homebrew, Homebrew/homebrew-core and Homebrew/homebrew-cask (if tapped) to standard output' + 'alias:Show existing aliases' 'analytics:Control Homebrew'\''s anonymous aggregate user behaviour analytics' 'audit:Check formula or cask for Homebrew coding style violations' 'autoremove:Uninstall formulae that were only installed as a dependency of another formula and are now no longer needed' @@ -219,6 +220,7 @@ __brew_internal_commands() { 'test:Run the test method provided by an installed formula' 'tests:Run Homebrew'\''s unit and integration tests' 'typecheck:Check for typechecking errors using Sorbet' + 'unalias:Remove aliases' 'unbottled:Show the unbottled dependents of formulae' 'uninstall:Uninstall a formula or cask' 'unlink:Remove symlinks for formula from Homebrew'\''s prefix' @@ -432,6 +434,16 @@ _brew_abv() { '*::cask:__brew_casks' } +# brew alias +_brew_alias() { + _arguments \ + '--debug[Display any debugging information]' \ + '--edit[Edit aliases in a text editor. Either one or all aliases may be opened at once. If the given alias doesn'\''t exist it'\''ll be pre-populated with a template]' \ + '--help[Show this message]' \ + '--quiet[Make some output more quiet]' \ + '--verbose[Make some output more verbose]' +} + # brew analytics _brew_analytics() { _arguments \ @@ -716,7 +728,7 @@ _brew_contributions() { '--from[Date (ISO-8601 format) to start searching contributions. Omitting this flag searches the last year]' \ '--help[Show this message]' \ '--quiet[Make some output more quiet]' \ - '--repositories[Specify a comma-separated list of repositories to search. Supported repositories: `brew`, `core`, `cask`, `aliases`, `bundle`, `command-not-found`, `test-bot` and `services`. Omitting this flag, or specifying `--repositories=primary`, searches only the main repositories: brew,core,cask. Specifying `--repositories=all`, searches all repositories. ]' \ + '--repositories[Specify a comma-separated list of repositories to search. Supported repositories: `brew`, `core`, `cask`, `bundle`, `command-not-found`, `test-bot` and `services`. Omitting this flag, or specifying `--repositories=primary`, searches only the main repositories: brew,core,cask. Specifying `--repositories=all`, searches all repositories. ]' \ '--to[Date (ISO-8601 format) to stop searching contributions]' \ '--user[Specify a comma-separated list of GitHub usernames or email addresses to find contributions from. Omitting this flag searches maintainers]' \ '--verbose[Make some output more verbose]' @@ -1987,6 +1999,15 @@ _brew_typecheck() { '*::tap:__brew_any_tap' } +# brew unalias +_brew_unalias() { + _arguments \ + '--debug[Display any debugging information]' \ + '--help[Show this message]' \ + '--quiet[Make some output more quiet]' \ + '--verbose[Make some output more verbose]' +} + # brew unbottled _brew_unbottled() { _arguments \ diff --git a/docs/Brew-Livecheck.md b/docs/Brew-Livecheck.md index a328437a1c..65cd546248 100644 --- a/docs/Brew-Livecheck.md +++ b/docs/Brew-Livecheck.md @@ -112,6 +112,24 @@ end The referenced formula/cask should be in the same tap, as a reference to a formula/cask from another tap will generate an error if the user doesn't already have it tapped. +### `POST` requests + +Some checks require making a `POST` request and that can be accomplished by adding a `post_form` or `post_json` option to a `livecheck` block `url`. + +```ruby +livecheck do + url "https://example.com/download.php", post_form: { + "Name" => "", + "E-mail" => "", + } + regex(/href=.*?example[._-]v?(\d+(?:\.\d+)+)\.t/i) +end +``` + +`post_form` is used for form data and `post_json` is used for JSON data. livecheck will encode the provided hash value to the appropriate format before making the request. + +`POST` support only applies to strategies that use `Strategy::page_headers` or `::page_content` (directly or indirectly), so it does not apply to `ExtractPlist`, `Git`, `GithubLatest`, `GithubReleases`, etc. + ### `strategy` blocks If the upstream version format needs to be manipulated to match the formula/cask format, a `strategy` block can be used instead of a `regex`. diff --git a/docs/How-to-Create-and-Maintain-a-Tap.md b/docs/How-to-Create-and-Maintain-a-Tap.md index 80ee676bf2..2f3342a4d8 100644 --- a/docs/How-to-Create-and-Maintain-a-Tap.md +++ b/docs/How-to-Create-and-Maintain-a-Tap.md @@ -50,7 +50,7 @@ Unlike formulae, casks must have globally unique names to avoid clashes. This ca You can provide your tap users with custom `brew` commands by adding them in a `cmd` subdirectory. [Read more on external commands](External-Commands.md). -See [homebrew/aliases](https://github.com/Homebrew/homebrew-aliases) for an example of a tap with external commands. +See [Homebrew/test-bot](https://github.com/Homebrew/homebrew-test-bot) for an example of a tap with external commands. ## Upstream taps diff --git a/docs/Manpage.md b/docs/Manpage.md index cba9428744..e712c08f71 100644 --- a/docs/Manpage.md +++ b/docs/Manpage.md @@ -102,6 +102,15 @@ If no search term is provided, all locally available formulae are listed. ## COMMANDS +### `alias` \[*`alias`* ... \| *`alias`*=*`command`*\] + +Show existing aliases. If no aliases are given, print the whole list. + +`--edit` + +: Edit aliases in a text editor. Either one or all aliases may be opened at + once. If the given alias doesn't exist it'll be pre-populated with a template. + ### `analytics` \[*`subcommand`*\] Control Homebrew's anonymous aggregate user behaviour analytics. Read more at @@ -1259,6 +1268,10 @@ provided, display brief statistics for all installed taps. accepted value for *`version`* is `v1`. See the docs for examples of using the JSON output: +### `unalias` *`alias`* \[...\] + +Remove aliases. + ### `uninstall`, `remove`, `rm` \[*`options`*\] *`installed_formula`*\|*`installed_cask`* \[...\] Uninstall a *`formula`* or *`cask`*. @@ -2066,10 +2079,10 @@ Summarise contributions to Homebrew repositories. `--repositories` : Specify a comma-separated list of repositories to search. Supported - repositories: `brew`, `core`, `cask`, `aliases`, `bundle`, - `command-not-found`, `test-bot` and `services`. Omitting this flag, or - specifying `--repositories=primary`, searches only the main repositories: - brew,core,cask. Specifying `--repositories=all`, searches all repositories. + repositories: `brew`, `core`, `cask`, `bundle`, `command-not-found`, + `test-bot` and `services`. Omitting this flag, or specifying + `--repositories=primary`, searches only the main repositories: brew,core,cask. + Specifying `--repositories=all`, searches all repositories. `--from` @@ -3102,15 +3115,6 @@ These options are applicable across multiple subcommands. ## OFFICIAL EXTERNAL COMMANDS -### `alias` \[*`alias`* ... \| *`alias`*=*`command`*\] - -Show existing aliases. If no aliases are given, print the whole list. - -`--edit` - -: Edit aliases in a text editor. Either one or all aliases may be opened at - once. If the given alias doesn't exist it'll be pre-populated with a template. - ### `bundle` \[*`subcommand`*\] Bundler for non-Ruby dependencies from Homebrew, Homebrew Cask, Mac App Store, @@ -3536,10 +3540,6 @@ and Linux workers. : Use these tested formulae from formulae steps for a formulae dependents step. -### `unalias` *`alias`* \[...\] - -Remove aliases. - ### `which-formula` \[`--explain`\] *`command`* \[...\] Show which formula(e) provides the given command. diff --git a/manpages/brew.1 b/manpages/brew.1 index 3dfd164deb..7a7755e8ce 100644 --- a/manpages/brew.1 +++ b/manpages/brew.1 @@ -62,6 +62,11 @@ List all installed formulae\. .SS "\fBsearch\fP \fR[\fItext\fP|\fB/\fP\fItext\fP\fB/\fP]" Perform a substring search of cask tokens and formula names for \fItext\fP\&\. If \fItext\fP is flanked by slashes, it is interpreted as a regular expression\. The search for \fItext\fP is extended online to \fBhomebrew/core\fP and \fBhomebrew/cask\fP\&\. If no search term is provided, all locally available formulae are listed\. .SH "COMMANDS" +.SS "\fBalias\fP \fR[\fIalias\fP \.\.\. | \fIalias\fP=\fIcommand\fP]" +Show existing aliases\. If no aliases are given, print the whole list\. +.TP +\fB\-\-edit\fP +Edit aliases in a text editor\. Either one or all aliases may be opened at once\. If the given alias doesn\[u2019]t exist it\[u2019]ll be pre\-populated with a template\. .SS "\fBanalytics\fP \fR[\fIsubcommand\fP]" Control Homebrew\[u2019]s anonymous aggregate user behaviour analytics\. Read more at .UR https://docs\.brew\.sh/Analytics @@ -788,6 +793,8 @@ Show information on each installed tap\. Print a JSON representation of \fItap\fP\&\. Currently the default and only accepted value for \fIversion\fP is \fBv1\fP\&\. See the docs for examples of using the JSON output: .UR https://docs\.brew\.sh/Querying\-Brew .UE +.SS "\fBunalias\fP \fIalias\fP \fR[\.\.\.]" +Remove aliases\. .SS "\fBuninstall\fP, \fBremove\fP, \fBrm\fP \fR[\fIoptions\fP] \fIinstalled_formula\fP|\fIinstalled_cask\fP \fR[\.\.\.]" Uninstall a \fIformula\fP or \fIcask\fP\&\. .TP @@ -1310,7 +1317,7 @@ Treat all named arguments as casks\. Summarise contributions to Homebrew repositories\. .TP \fB\-\-repositories\fP -Specify a comma\-separated list of repositories to search\. Supported repositories: \fBbrew\fP, \fBcore\fP, \fBcask\fP, \fBaliases\fP, \fBbundle\fP, \fBcommand\-not\-found\fP, \fBtest\-bot\fP and \fBservices\fP\&\. Omitting this flag, or specifying \fB\-\-repositories=primary\fP, searches only the main repositories: brew,core,cask\. Specifying \fB\-\-repositories=all\fP, searches all repositories\. +Specify a comma\-separated list of repositories to search\. Supported repositories: \fBbrew\fP, \fBcore\fP, \fBcask\fP, \fBbundle\fP, \fBcommand\-not\-found\fP, \fBtest\-bot\fP and \fBservices\fP\&\. Omitting this flag, or specifying \fB\-\-repositories=primary\fP, searches only the main repositories: brew,core,cask\. Specifying \fB\-\-repositories=all\fP, searches all repositories\. .TP \fB\-\-from\fP Date (ISO\-8601 format) to start searching contributions\. Omitting this flag searches the last year\. @@ -1989,11 +1996,6 @@ Make some output more verbose\. \fB\-h\fP, \fB\-\-help\fP Show this message\. .SH "OFFICIAL EXTERNAL COMMANDS" -.SS "\fBalias\fP \fR[\fIalias\fP \.\.\. | \fIalias\fP=\fIcommand\fP]" -Show existing aliases\. If no aliases are given, print the whole list\. -.TP -\fB\-\-edit\fP -Edit aliases in a text editor\. Either one or all aliases may be opened at once\. If the given alias doesn\[u2019]t exist it\[u2019]ll be pre\-populated with a template\. .SS "\fBbundle\fP \fR[\fIsubcommand\fP]" Bundler for non\-Ruby dependencies from Homebrew, Homebrew Cask, Mac App Store, Whalebrew and Visual Studio Code\. .TP @@ -2269,8 +2271,6 @@ Use these skipped or failed formulae from formulae steps for a formulae dependen .TP \fB\-\-tested\-formulae\fP Use these tested formulae from formulae steps for a formulae dependents step\. -.SS "\fBunalias\fP \fIalias\fP \fR[\.\.\.]" -Remove aliases\. .SS "\fBwhich\-formula\fP \fR[\fB\-\-explain\fP] \fIcommand\fP \fR[\.\.\.]" Show which formula(e) provides the given command\. .TP