From 91ad24b8761d22c6c2cad933eae7c17198a2cfdd Mon Sep 17 00:00:00 2001 From: Douglas Eichelberger Date: Tue, 28 Feb 2023 16:37:13 -0800 Subject: [PATCH 01/10] Remove Array#to_sentence monkey-patch --- Library/Homebrew/cask/artifact/pkg.rb | 2 +- Library/Homebrew/cask/auditor.rb | 5 ++- Library/Homebrew/cask/cmd/uninstall.rb | 2 +- Library/Homebrew/cask/installer.rb | 2 +- Library/Homebrew/cli/parser.rb | 12 ++--- Library/Homebrew/cmd/developer.rb | 5 ++- Library/Homebrew/cmd/update-report.rb | 2 +- Library/Homebrew/dev-cmd/audit.rb | 2 +- Library/Homebrew/dev-cmd/contributions.rb | 3 +- .../Homebrew/dev-cmd/update-maintainers.rb | 2 +- Library/Homebrew/dev-cmd/update-sponsors.rb | 2 +- Library/Homebrew/diagnostic.rb | 2 +- Library/Homebrew/exceptions.rb | 8 ++-- Library/Homebrew/formula_installer.rb | 4 +- Library/Homebrew/global.rb | 3 +- Library/Homebrew/rubocops/all.rb | 3 -- Library/Homebrew/rubocops/components_order.rb | 3 ++ Library/Homebrew/tap.rb | 4 +- Library/Homebrew/uninstall.rb | 8 ++-- Library/Homebrew/utils.rb | 45 +++++++++++++++++++ 20 files changed, 85 insertions(+), 34 deletions(-) diff --git a/Library/Homebrew/cask/artifact/pkg.rb b/Library/Homebrew/cask/artifact/pkg.rb index 924f731ae4..8d903922ca 100644 --- a/Library/Homebrew/cask/artifact/pkg.rb +++ b/Library/Homebrew/cask/artifact/pkg.rb @@ -46,7 +46,7 @@ module Cask pkgs = Pathname.glob(cask.staged_path/"**"/"*.pkg").map { |path| path.relative_path_from(cask.staged_path) } message = "Could not find PKG source file '#{pkg}'" - message += ", found #{pkgs.map { |path| "'#{path}'" }.to_sentence} instead" if pkgs.any? + message += ", found #{::Utils.to_sentence(pkgs.map { |path| "'#{path}'" })} instead" if pkgs.any? message += "." raise CaskError, message diff --git a/Library/Homebrew/cask/auditor.rb b/Library/Homebrew/cask/auditor.rb index 0969a7dc30..80bdb5f839 100644 --- a/Library/Homebrew/cask/auditor.rb +++ b/Library/Homebrew/cask/auditor.rb @@ -57,7 +57,8 @@ module Cask if !language && language_blocks sample_languages = if language_blocks.length > LANGUAGE_BLOCK_LIMIT && !@audit_new_cask sample_keys = language_blocks.keys.sample(LANGUAGE_BLOCK_LIMIT) - ohai "Auditing a sample of available languages: #{sample_keys.map { |lang| lang[0].to_s }.to_sentence}" + sample_keys_sentence = ::Utils.to_sentence(sample_keys.map { |lang| lang[0].to_s }) + ohai "Auditing a sample of available languages: #{sameple_keys_sentence}" language_blocks.select { |k| sample_keys.include?(k) } else language_blocks @@ -67,7 +68,7 @@ module Cask audit = audit_languages(l) summary = audit.summary(include_passed: output_passed?, include_warnings: output_warnings?) if summary.present? && output_summary?(audit) - ohai "Auditing language: #{l.map { |lang| "'#{lang}'" }.to_sentence}" if output_summary? + ohai "Auditing language: #{::Utils.to_sentence(l.map { |lang| "'#{lang}'" })}" if output_summary? puts summary end warnings += audit.warnings diff --git a/Library/Homebrew/cask/cmd/uninstall.rb b/Library/Homebrew/cask/cmd/uninstall.rb index 1700482a65..e6874f2666 100644 --- a/Library/Homebrew/cask/cmd/uninstall.rb +++ b/Library/Homebrew/cask/cmd/uninstall.rb @@ -46,7 +46,7 @@ module Cask next if (versions = cask.versions).empty? puts <<~EOS - #{cask} #{versions.to_sentence} #{(versions.count == 1) ? "is" : "are"} still installed. + #{cask} #{::Utils.to_sentence(versions)} #{(versions.count == 1) ? "is" : "are"} still installed. Remove #{(versions.count == 1) ? "it" : "them all"} with `brew uninstall --cask --force #{cask}`. EOS end diff --git a/Library/Homebrew/cask/installer.rb b/Library/Homebrew/cask/installer.rb index 03e49a9744..465cf18982 100644 --- a/Library/Homebrew/cask/installer.rb +++ b/Library/Homebrew/cask/installer.rb @@ -306,7 +306,7 @@ module Cask rescue TSort::Cyclic strongly_connected_components = graph.strongly_connected_components.sort_by(&:count) cyclic_dependencies = strongly_connected_components.last - [@cask] - raise CaskCyclicDependencyError.new(@cask.token, cyclic_dependencies.to_sentence) + raise CaskCyclicDependencyError.new(@cask.token, ::Utils.to_sentence(cyclic_dependencies)) end end diff --git a/Library/Homebrew/cli/parser.rb b/Library/Homebrew/cli/parser.rb index 568ab62d06..64d19f8ef2 100644 --- a/Library/Homebrew/cli/parser.rb +++ b/Library/Homebrew/cli/parser.rb @@ -683,8 +683,8 @@ module Homebrew "This command does not take named arguments." else types << :named if types.empty? - arg_types = types.map { |type| type.to_s.tr("_", " ") } - .to_sentence two_words_connector: " or ", last_word_connector: " or " + arg_types = Utils.to_sentence(types.map { |type| type.to_s.tr("_", " ") }, + two_words_connector: " or ", last_word_connector: " or ") "This command does not take more than #{maximum} #{arg_types} #{Utils.pluralize("argument", maximum)}." end @@ -697,8 +697,8 @@ module Homebrew sig { params(minimum: Integer, types: T::Array[Symbol]).void } def initialize(minimum, types: []) types << :named if types.empty? - arg_types = types.map { |type| type.to_s.tr("_", " ") } - .to_sentence two_words_connector: " or ", last_word_connector: " or " + arg_types = Utils.to_sentence(types.map { |type| type.to_s.tr("_", " ") }, + two_words_connector: " or ", last_word_connector: " or ") super "This command requires at least #{minimum} #{arg_types} #{Utils.pluralize("argument", minimum)}." end @@ -710,8 +710,8 @@ module Homebrew sig { params(minimum: Integer, types: T::Array[Symbol]).void } def initialize(minimum, types: []) types << :named if types.empty? - arg_types = types.map { |type| type.to_s.tr("_", " ") } - .to_sentence two_words_connector: " or ", last_word_connector: " or " + arg_types = Utils.to_sentence(types.map { |type| type.to_s.tr("_", " ") }, + two_words_connector: " or ", last_word_connector: " or ") super "This command requires exactly #{minimum} #{arg_types} #{Utils.pluralize("argument", minimum)}." end diff --git a/Library/Homebrew/cmd/developer.rb b/Library/Homebrew/cmd/developer.rb index 131f18cef5..028010707c 100755 --- a/Library/Homebrew/cmd/developer.rb +++ b/Library/Homebrew/cmd/developer.rb @@ -40,7 +40,8 @@ module Homebrew case args.named.first when nil, "state" if env_vars.any? - puts "Developer mode is enabled because #{env_vars.to_sentence} #{(env_vars.count == 1) ? "is" : "are"} set." + env_vars_str = Utils.to_sentence(env_vars) + puts "Developer mode is enabled because #{env_vars_str} #{(env_vars.count == 1) ? "is" : "are"} set." elsif Homebrew::Settings.read("devcmdrun") == "true" puts "Developer mode is enabled." else @@ -50,7 +51,7 @@ module Homebrew Homebrew::Settings.write "devcmdrun", true when "off" Homebrew::Settings.delete "devcmdrun" - puts "To fully disable developer mode, you must unset #{env_vars.to_sentence}." if env_vars.any? + puts "To fully disable developer mode, you must unset #{Utils.to_sentence(env_vars)}." if env_vars.any? else raise UsageError, "unknown subcommand: #{args.named.first}" end diff --git a/Library/Homebrew/cmd/update-report.rb b/Library/Homebrew/cmd/update-report.rb index 22306001bf..e75ede46ec 100644 --- a/Library/Homebrew/cmd/update-report.rb +++ b/Library/Homebrew/cmd/update-report.rb @@ -234,7 +234,7 @@ module Homebrew unless updated_taps.empty? auto_update_header args: args noun = Utils.pluralize("tap", updated_taps.count) - puts "Updated #{updated_taps.count} #{noun} (#{updated_taps.to_sentence})." + puts "Updated #{updated_taps.count} #{noun} (#{Utils.to_sentence(updated_taps)})." updated = true end diff --git a/Library/Homebrew/dev-cmd/audit.rb b/Library/Homebrew/dev-cmd/audit.rb index c5dac1cb53..40da9791ec 100644 --- a/Library/Homebrew/dev-cmd/audit.rb +++ b/Library/Homebrew/dev-cmd/audit.rb @@ -286,7 +286,7 @@ module Homebrew error_sources << "#{cask_count} #{Utils.pluralize("cask", cask_count)}" if cask_count.positive? error_sources << "#{tap_count} #{Utils.pluralize("tap", tap_count)}" if tap_count.positive? - errors_summary += " in #{error_sources.to_sentence}" if error_sources.any? + errors_summary += " in #{Utils.to_sentence(error_sources)}" if error_sources.any? errors_summary += " detected" diff --git a/Library/Homebrew/dev-cmd/contributions.rb b/Library/Homebrew/dev-cmd/contributions.rb index 9fe19f8b59..4129f41e43 100755 --- a/Library/Homebrew/dev-cmd/contributions.rb +++ b/Library/Homebrew/dev-cmd/contributions.rb @@ -18,6 +18,7 @@ module Homebrew sig { returns(CLI::Parser) } def contributions_args + supported_repos_sentence = Utils.to_sentence(SUPPORTED_REPOS.map { |t| "`#{t}`" }) Homebrew::CLI::Parser.new do usage_banner "`contributions` [--user=] [<--repositories>`=`] [<--csv>]" description <<~EOS @@ -26,7 +27,7 @@ module Homebrew comma_array "--repositories", description: "Specify a comma-separated (no spaces) list of repositories to search. " \ - "Supported repositories: #{SUPPORTED_REPOS.map { |t| "`#{t}`" }.to_sentence}. " \ + "Supported repositories: #{supported_repos_sentence}. " \ "Omitting this flag, or specifying `--repositories=all`, searches all repositories. " \ "Use `--repositories=primary` to search only the main repositories: brew,core,cask." flag "--from=", diff --git a/Library/Homebrew/dev-cmd/update-maintainers.rb b/Library/Homebrew/dev-cmd/update-maintainers.rb index 2b76426e55..6185a072c4 100644 --- a/Library/Homebrew/dev-cmd/update-maintainers.rb +++ b/Library/Homebrew/dev-cmd/update-maintainers.rb @@ -37,7 +37,7 @@ module Homebrew members.each do |group, hash| hash.slice!(*public_members) hash.each { |login, name| hash[login] = "[#{name}](https://github.com/#{login})" } - sentences[group] = hash.values.sort.to_sentence + sentences[group] = Utils.to_sentence(hash.values.sort) end readme = HOMEBREW_REPOSITORY/"README.md" diff --git a/Library/Homebrew/dev-cmd/update-sponsors.rb b/Library/Homebrew/dev-cmd/update-sponsors.rb index dc5b62b277..93879ce3ac 100644 --- a/Library/Homebrew/dev-cmd/update-sponsors.rb +++ b/Library/Homebrew/dev-cmd/update-sponsors.rb @@ -57,7 +57,7 @@ module Homebrew readme = HOMEBREW_REPOSITORY/"README.md" content = readme.read - content.gsub!(/(Homebrew is generously supported by) .*\Z/m, "\\1 #{named_sponsors.to_sentence}.\n") + content.gsub!(/(Homebrew is generously supported by) .*\Z/m, "\\1 #{Utils.to_sentence(named_sponsors)}.\n") content << "\n#{logo_sponsors.join}\n" if logo_sponsors.presence File.write(readme, content) diff --git a/Library/Homebrew/diagnostic.rb b/Library/Homebrew/diagnostic.rb index 3d1c512a22..4e12603528 100644 --- a/Library/Homebrew/diagnostic.rb +++ b/Library/Homebrew/diagnostic.rb @@ -911,7 +911,7 @@ module Homebrew end) taps = Utils.pluralize("tap", error_tap_paths.count) - "Unable to read from cask #{taps}: #{error_tap_paths.to_sentence}" if error_tap_paths.present? + "Unable to read from cask #{taps}: #{Utils.to_sentence(error_tap_paths)}" if error_tap_paths.present? end def check_cask_load_path diff --git a/Library/Homebrew/exceptions.rb b/Library/Homebrew/exceptions.rb index 7f8b4f9d6b..56cb1da56b 100644 --- a/Library/Homebrew/exceptions.rb +++ b/Library/Homebrew/exceptions.rb @@ -100,7 +100,8 @@ class FormulaOrCaskUnavailableError < RuntimeError similar_formula_names = Formula.fuzzy_search(name) return "" if similar_formula_names.blank? - "Did you mean #{similar_formula_names.to_sentence two_words_connector: " or ", last_word_connector: " or "}?" + "Did you mean #{Utils.to_sentence(similar_formula_names, two_words_connector: " or ", + last_word_connector: " or ")}?" end sig { returns(String) } @@ -570,7 +571,7 @@ class UnbottledError < RuntimeError msg = +<<~EOS The following #{Utils.pluralize("formula", formulae.count, plural: "e")} cannot be installed from #{Utils.pluralize("bottle", formulae.count)} and must be built from source. - #{formulae.to_sentence} + #{Utils.to_sentence(formulae)} EOS msg += "#{DevelopmentTools.installation_instructions}\n" unless DevelopmentTools.installed? msg.freeze @@ -804,7 +805,8 @@ class CyclicDependencyError < RuntimeError def initialize(strongly_connected_components) super <<~EOS The following packages contain cyclic dependencies: - #{strongly_connected_components.select { |packages| packages.count > 1 }.map(&:to_sentence).join("\n ")} + #{strongly_connected_components.select { |packages| packages.count > 1 } + .map { |p| Utils.to_sentence(p) }.join("\n ")} EOS end end diff --git a/Library/Homebrew/formula_installer.rb b/Library/Homebrew/formula_installer.rb index 12eb8ec499..1af0800d3b 100644 --- a/Library/Homebrew/formula_installer.rb +++ b/Library/Homebrew/formula_installer.rb @@ -652,7 +652,7 @@ class FormulaInstaller puts "All dependencies for #{formula.full_name} are satisfied." elsif !deps.empty? oh1 "Installing dependencies for #{formula.full_name}: " \ - "#{deps.map(&:first).map(&Formatter.method(:identifier)).to_sentence}", + "#{Utils.to_sentence(deps.map(&:first).map(&Formatter.method(:identifier)))}", truncate: false deps.each { |dep, options| install_dependency(dep, options) } end @@ -1163,7 +1163,7 @@ class FormulaInstaller return if deps.empty? oh1 "Fetching dependencies for #{formula.full_name}: " \ - "#{deps.map(&:first).map(&Formatter.method(:identifier)).to_sentence}", + "#{Utils.to_sentence(deps.map(&:first).map(&Formatter.method(:identifier)))}", truncate: false deps.each { |dep, _options| fetch_dependency(dep) } diff --git a/Library/Homebrew/global.rb b/Library/Homebrew/global.rb index c773b8bb0f..fb3d785efa 100644 --- a/Library/Homebrew/global.rb +++ b/Library/Homebrew/global.rb @@ -17,7 +17,8 @@ require "active_support/core_ext/string/filters" require "active_support/core_ext/object/try" require "active_support/core_ext/array/access" require "active_support/core_ext/string/inflections" -require "active_support/core_ext/array/conversions" +require "active_support/core_ext/kernel/reporting" +require "active_support/core_ext/hash/keys" require "active_support/core_ext/hash/deep_merge" require "active_support/core_ext/file/atomic" require "active_support/core_ext/enumerable" diff --git a/Library/Homebrew/rubocops/all.rb b/Library/Homebrew/rubocops/all.rb index ba9442d61c..96a9f5176c 100644 --- a/Library/Homebrew/rubocops/all.rb +++ b/Library/Homebrew/rubocops/all.rb @@ -1,9 +1,6 @@ # typed: strict # frozen_string_literal: true -# TODO: remove this (and avoid further active support in rubocops) -require "active_support/core_ext/array/conversions" - require_relative "io_read" require_relative "move_to_extend_os" require_relative "shell_commands" diff --git a/Library/Homebrew/rubocops/components_order.rb b/Library/Homebrew/rubocops/components_order.rb index 4661aed6fa..e07af84e11 100644 --- a/Library/Homebrew/rubocops/components_order.rb +++ b/Library/Homebrew/rubocops/components_order.rb @@ -4,6 +4,9 @@ require "ast_constants" require "rubocops/extend/formula_cop" +# TODO: remove this (and avoid further active support in rubocops) +require "active_support/core_ext/array/conversions" + module RuboCop module Cop module FormulaAudit diff --git a/Library/Homebrew/tap.rb b/Library/Homebrew/tap.rb index 457fe67726..21bad4bf22 100644 --- a/Library/Homebrew/tap.rb +++ b/Library/Homebrew/tap.rb @@ -326,7 +326,7 @@ class Tap Commands.rebuild_commands_completion_list link_completions_and_manpages - formatted_contents = contents.presence&.to_sentence&.dup&.prepend(" ") + formatted_contents = contents.presence&.then { |c| Utils.to_sentence(c) }&.dup&.prepend(" ") $stderr.puts "Tapped#{formatted_contents} (#{path.abv})." unless quiet CacheStoreDatabase.use(:descriptions) do |db| DescriptionCacheStore.new(db) @@ -414,7 +414,7 @@ class Tap $stderr.puts "Untapping #{name}..." abv = path.abv - formatted_contents = contents.presence&.to_sentence&.dup&.prepend(" ") + formatted_contents = contents.presence&.then { |c| Utils.to_sentence(c) }&.dup&.prepend(" ") CacheStoreDatabase.use(:descriptions) do |db| DescriptionCacheStore.new(db) diff --git a/Library/Homebrew/uninstall.rb b/Library/Homebrew/uninstall.rb index a6f366f448..4e7c9eff8f 100644 --- a/Library/Homebrew/uninstall.rb +++ b/Library/Homebrew/uninstall.rb @@ -52,7 +52,7 @@ module Homebrew if rack.directory? versions = rack.subdirs.map(&:basename) puts <<~EOS - #{keg.name} #{versions.to_sentence} #{(versions.count == 1) ? "is" : "are"} still installed. + #{keg.name} #{Utils.to_sentence(versions)} #{(versions.count == 1) ? "is" : "are"} still installed. To remove all versions, run: brew uninstall --force #{keg.name} EOS @@ -136,7 +136,7 @@ module Homebrew end def are_required_by_deps - "#{(reqs.count == 1) ? "is" : "are"} required by #{deps.to_sentence}, " \ + "#{(reqs.count == 1) ? "is" : "are"} required by #{Utils.to_sentence(deps)}, " \ "which #{(deps.count == 1) ? "is" : "are"} currently installed" end end @@ -145,7 +145,7 @@ module Homebrew class DeveloperDependentsMessage < DependentsMessage def output opoo <<~EOS - #{reqs.to_sentence} #{are_required_by_deps}. + #{Utils.to_sentence(reqs)} #{are_required_by_deps}. You can silence this warning with: #{sample_command} EOS @@ -156,7 +156,7 @@ module Homebrew class NondeveloperDependentsMessage < DependentsMessage def output ofail <<~EOS - Refusing to uninstall #{reqs.to_sentence} + Refusing to uninstall #{Utils.to_sentence(reqs)} because #{(reqs.count == 1) ? "it" : "they"} #{are_required_by_deps}. You can override this and force removal with: #{sample_command} diff --git a/Library/Homebrew/utils.rb b/Library/Homebrew/utils.rb index 3ca0e63535..a6435344f0 100644 --- a/Library/Homebrew/utils.rb +++ b/Library/Homebrew/utils.rb @@ -133,4 +133,49 @@ module Utils suffix = (count == 1) ? singular : plural "#{stem}#{suffix}" end + + # Converts the array to a comma-separated sentence where the last element is + # joined by the connector word. + # + # You can pass the following options to change the default behavior. If you + # pass an option key that doesn't exist in the list below, it will raise an + # ArgumentError. + # + # ==== Options + # + # * :words_connector - The sign or word used to join all but the last + # element in arrays with three or more elements (default: ", "). + # * :last_word_connector - The sign or word used to join the last element + # in arrays with three or more elements (default: ", and "). + # * :two_words_connector - The sign or word used to join the elements + # in arrays with two elements (default: " and "). + # + # ==== Examples + # + # [].to_sentence # => "" + # ['one'].to_sentence # => "one" + # ['one', 'two'].to_sentence # => "one and two" + # ['one', 'two', 'three'].to_sentence # => "one, two, and three" + # + # ['one', 'two'].to_sentence(two_words_connector: '-') + # # => "one-two" + # + # ['one', 'two', 'three'].to_sentence(words_connector: ' or ', last_word_connector: ' or at least ') + # # => "one or two or at least three" + sig { + params(array: T::Array[String], words_connector: String, two_words_connector: String, last_word_connector: String) + .returns(String) + } + def self.to_sentence(array, words_connector: ", ", two_words_connector: " and ", last_word_connector: ", and ") + case array.length + when 0 + +"" + when 1 + +(array[0]).to_s + when 2 + +"#{array[0]}#{two_words_connector}#{array[1]}" + else + +"#{array[0...-1].join(words_connector)}#{last_word_connector}#{array[-1]}" + end + end end From d8d4d2031ed5b98f30980354bd16b911c53cb404 Mon Sep 17 00:00:00 2001 From: Douglas Eichelberger Date: Tue, 28 Feb 2023 17:21:40 -0800 Subject: [PATCH 02/10] Isolate to allow rubocop access --- Library/Homebrew/rubocops/components_order.rb | 4 +- Library/Homebrew/utils.rb | 46 +--------------- Library/Homebrew/utils/array.rb | 53 +++++++++++++++++++ 3 files changed, 55 insertions(+), 48 deletions(-) create mode 100644 Library/Homebrew/utils/array.rb diff --git a/Library/Homebrew/rubocops/components_order.rb b/Library/Homebrew/rubocops/components_order.rb index e07af84e11..c84766adc4 100644 --- a/Library/Homebrew/rubocops/components_order.rb +++ b/Library/Homebrew/rubocops/components_order.rb @@ -3,9 +3,7 @@ require "ast_constants" require "rubocops/extend/formula_cop" - -# TODO: remove this (and avoid further active support in rubocops) -require "active_support/core_ext/array/conversions" +require "utils/array" module RuboCop module Cop diff --git a/Library/Homebrew/utils.rb b/Library/Homebrew/utils.rb index a6435344f0..f231d698f5 100644 --- a/Library/Homebrew/utils.rb +++ b/Library/Homebrew/utils.rb @@ -4,6 +4,7 @@ require "time" require "utils/analytics" +require "utils/array" require "utils/curl" require "utils/fork" require "utils/formatter" @@ -133,49 +134,4 @@ module Utils suffix = (count == 1) ? singular : plural "#{stem}#{suffix}" end - - # Converts the array to a comma-separated sentence where the last element is - # joined by the connector word. - # - # You can pass the following options to change the default behavior. If you - # pass an option key that doesn't exist in the list below, it will raise an - # ArgumentError. - # - # ==== Options - # - # * :words_connector - The sign or word used to join all but the last - # element in arrays with three or more elements (default: ", "). - # * :last_word_connector - The sign or word used to join the last element - # in arrays with three or more elements (default: ", and "). - # * :two_words_connector - The sign or word used to join the elements - # in arrays with two elements (default: " and "). - # - # ==== Examples - # - # [].to_sentence # => "" - # ['one'].to_sentence # => "one" - # ['one', 'two'].to_sentence # => "one and two" - # ['one', 'two', 'three'].to_sentence # => "one, two, and three" - # - # ['one', 'two'].to_sentence(two_words_connector: '-') - # # => "one-two" - # - # ['one', 'two', 'three'].to_sentence(words_connector: ' or ', last_word_connector: ' or at least ') - # # => "one or two or at least three" - sig { - params(array: T::Array[String], words_connector: String, two_words_connector: String, last_word_connector: String) - .returns(String) - } - def self.to_sentence(array, words_connector: ", ", two_words_connector: " and ", last_word_connector: ", and ") - case array.length - when 0 - +"" - when 1 - +(array[0]).to_s - when 2 - +"#{array[0]}#{two_words_connector}#{array[1]}" - else - +"#{array[0...-1].join(words_connector)}#{last_word_connector}#{array[-1]}" - end - end end diff --git a/Library/Homebrew/utils/array.rb b/Library/Homebrew/utils/array.rb new file mode 100644 index 0000000000..7fad9154a9 --- /dev/null +++ b/Library/Homebrew/utils/array.rb @@ -0,0 +1,53 @@ +# typed: strict +# frozen_string_literal: true + +module Utils + extend T::Sig + + # Converts the array to a comma-separated sentence where the last element is + # joined by the connector word. + # + # You can pass the following options to change the default behavior. If you + # pass an option key that doesn't exist in the list below, it will raise an + # ArgumentError. + # + # ==== Options + # + # * :words_connector - The sign or word used to join all but the last + # element in arrays with three or more elements (default: ", "). + # * :last_word_connector - The sign or word used to join the last element + # in arrays with three or more elements (default: ", and "). + # * :two_words_connector - The sign or word used to join the elements + # in arrays with two elements (default: " and "). + # + # ==== Examples + # + # [].to_sentence # => "" + # ['one'].to_sentence # => "one" + # ['one', 'two'].to_sentence # => "one and two" + # ['one', 'two', 'three'].to_sentence # => "one, two, and three" + # + # ['one', 'two'].to_sentence(two_words_connector: '-') + # # => "one-two" + # + # ['one', 'two', 'three'].to_sentence(words_connector: ' or ', last_word_connector: ' or at least ') + # # => "one or two or at least three" + # @see https://github.com/rails/rails/blob/v6.1.7.2/activesupport/lib/active_support/core_ext/array/conversions.rb#L10-L85 + # ActiveSupport implementation + sig { + params(array: T::Array[String], words_connector: String, two_words_connector: String, last_word_connector: String) + .returns(String) + } + def self.to_sentence(array, words_connector: ", ", two_words_connector: " and ", last_word_connector: ", and ") + case array.length + when 0 + +"" + when 1 + +(array[0]).to_s + when 2 + +"#{array[0]}#{two_words_connector}#{array[1]}" + else + +"#{array[0...-1].join(words_connector)}#{last_word_connector}#{array[-1]}" + end + end +end From 9ab3cfb7a77b68f873bdcbe9405aeb54cfc61047 Mon Sep 17 00:00:00 2001 From: Douglas Eichelberger Date: Tue, 28 Feb 2023 17:36:12 -0800 Subject: [PATCH 03/10] Add tests --- Library/Homebrew/cask/auditor.rb | 2 +- Library/Homebrew/test/utils_spec.rb | 47 +++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+), 1 deletion(-) diff --git a/Library/Homebrew/cask/auditor.rb b/Library/Homebrew/cask/auditor.rb index 80bdb5f839..caf2fa0ed8 100644 --- a/Library/Homebrew/cask/auditor.rb +++ b/Library/Homebrew/cask/auditor.rb @@ -58,7 +58,7 @@ module Cask sample_languages = if language_blocks.length > LANGUAGE_BLOCK_LIMIT && !@audit_new_cask sample_keys = language_blocks.keys.sample(LANGUAGE_BLOCK_LIMIT) sample_keys_sentence = ::Utils.to_sentence(sample_keys.map { |lang| lang[0].to_s }) - ohai "Auditing a sample of available languages: #{sameple_keys_sentence}" + ohai "Auditing a sample of available languages: #{sample_keys_sentence}" language_blocks.select { |k| sample_keys.include?(k) } else language_blocks diff --git a/Library/Homebrew/test/utils_spec.rb b/Library/Homebrew/test/utils_spec.rb index c75d21bed8..551f7d9c78 100644 --- a/Library/Homebrew/test/utils_spec.rb +++ b/Library/Homebrew/test/utils_spec.rb @@ -58,4 +58,51 @@ describe Utils do expect(described_class.pluralize("foo", 2, singular: "o", plural: "es")).to eq("fooes") end end + + describe ".to_sentence" do + it "converts a plain array to a sentence" do + expect(described_class.to_sentence([])).to eq("") + expect(described_class.to_sentence(["one"])).to eq("one") + expect(described_class.to_sentence(["one", "two"])).to eq("one and two") + expect(described_class.to_sentence(["one", "two", "three"])).to eq("one, two, and three") + end + + it "converts an array to a sentence with a custom connector" do + expect(described_class.to_sentence(["one", "two", "three"], words_connector: " ")).to eq("one two, and three") + expect(described_class.to_sentence(["one", "two", "three"], + words_connector: " & ")).to eq("one & two, and three") + end + + it "converts an array to a sentence with a custom last word connector" do + expect(described_class.to_sentence(["one", "two", "three"], + last_word_connector: ", and also ")).to eq("one, two, and also three") + expect(described_class.to_sentence(["one", "two", "three"], last_word_connector: " ")).to eq("one, two three") + expect(described_class.to_sentence(["one", "two", "three"], + last_word_connector: " and ")).to eq("one, two and three") + end + + it "converts an array to a sentence with a custom two word connector" do + expect(described_class.to_sentence(["one", "two"], two_words_connector: " ")).to eq("one two") + end + + it "creates a new string" do + elements = ["one"] + expect(described_class.to_sentence(elements).object_id).not_to eq(elements[0].object_id) + end + + it "converts a non-String to a sentence" do + expect(described_class.to_sentence([1])).to eq("1") + end + + it "converts an array with blank elements to a sentence" do + expect(described_class.to_sentence([nil, "one", "", "two", "three"])).to eq(", one, , two, and three") + end + + it "does not return a frozen string" do + expect(described_class.to_sentence([])).not_to be_frozen + expect(described_class.to_sentence(["one"])).not_to be_frozen + expect(described_class.to_sentence(["one", "two"])).not_to be_frozen + expect(described_class.to_sentence(["one", "two", "three"])).not_to be_frozen + end + end end From b8ddecf3220459bad5e6ce15098be6980d4b1eaf Mon Sep 17 00:00:00 2001 From: Douglas Eichelberger Date: Tue, 28 Feb 2023 18:03:42 -0800 Subject: [PATCH 04/10] Fix type error --- Library/Homebrew/rubocops/components_order.rb | 4 ++-- Library/Homebrew/utils/array.rb | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Library/Homebrew/rubocops/components_order.rb b/Library/Homebrew/rubocops/components_order.rb index c84766adc4..96204a58e0 100644 --- a/Library/Homebrew/rubocops/components_order.rb +++ b/Library/Homebrew/rubocops/components_order.rb @@ -76,8 +76,8 @@ module RuboCop [:url, :version, :sha256], [:url, :mirror, :version, :sha256], ] - minimum_methods = allowed_methods.first.map { |m| "`#{m}`" }.to_sentence - maximum_methods = allowed_methods.last.map { |m| "`#{m}`" }.to_sentence + minimum_methods = Utils.to_sentence(allowed_methods.first.map { |m| "`#{m}`" }) + maximum_methods = Utils.to_sentence(allowed_methods.last.map { |m| "`#{m}`" }) on_system_bodies.each do |on_system_block, on_system_body| method_name = on_system_block.method_name diff --git a/Library/Homebrew/utils/array.rb b/Library/Homebrew/utils/array.rb index 7fad9154a9..4b95bcf484 100644 --- a/Library/Homebrew/utils/array.rb +++ b/Library/Homebrew/utils/array.rb @@ -47,7 +47,7 @@ module Utils when 2 +"#{array[0]}#{two_words_connector}#{array[1]}" else - +"#{array[0...-1].join(words_connector)}#{last_word_connector}#{array[-1]}" + +"#{T.must(array[0...-1]).join(words_connector)}#{last_word_connector}#{array[-1]}" end end end From ecfceb3feeabc40706b7097cf9becb96c867c8cf Mon Sep 17 00:00:00 2001 From: Douglas Eichelberger Date: Thu, 2 Mar 2023 14:07:10 -0800 Subject: [PATCH 05/10] Revert Utils implementation --- Library/Homebrew/cask/artifact/pkg.rb | 2 +- Library/Homebrew/cask/auditor.rb | 5 +- Library/Homebrew/cask/cmd/uninstall.rb | 2 +- Library/Homebrew/cask/installer.rb | 2 +- Library/Homebrew/cli/parser.rb | 12 ++--- Library/Homebrew/cmd/developer.rb | 5 +- Library/Homebrew/cmd/update-report.rb | 2 +- Library/Homebrew/dev-cmd/audit.rb | 2 +- Library/Homebrew/dev-cmd/contributions.rb | 3 +- .../Homebrew/dev-cmd/update-maintainers.rb | 2 +- Library/Homebrew/dev-cmd/update-sponsors.rb | 2 +- Library/Homebrew/diagnostic.rb | 2 +- Library/Homebrew/exceptions.rb | 8 ++- Library/Homebrew/formula_installer.rb | 4 +- Library/Homebrew/rubocops/all.rb | 3 ++ Library/Homebrew/rubocops/components_order.rb | 5 +- Library/Homebrew/tap.rb | 4 +- Library/Homebrew/uninstall.rb | 8 +-- Library/Homebrew/utils.rb | 1 - Library/Homebrew/utils/array.rb | 53 ------------------- 20 files changed, 35 insertions(+), 92 deletions(-) delete mode 100644 Library/Homebrew/utils/array.rb diff --git a/Library/Homebrew/cask/artifact/pkg.rb b/Library/Homebrew/cask/artifact/pkg.rb index 8d903922ca..924f731ae4 100644 --- a/Library/Homebrew/cask/artifact/pkg.rb +++ b/Library/Homebrew/cask/artifact/pkg.rb @@ -46,7 +46,7 @@ module Cask pkgs = Pathname.glob(cask.staged_path/"**"/"*.pkg").map { |path| path.relative_path_from(cask.staged_path) } message = "Could not find PKG source file '#{pkg}'" - message += ", found #{::Utils.to_sentence(pkgs.map { |path| "'#{path}'" })} instead" if pkgs.any? + message += ", found #{pkgs.map { |path| "'#{path}'" }.to_sentence} instead" if pkgs.any? message += "." raise CaskError, message diff --git a/Library/Homebrew/cask/auditor.rb b/Library/Homebrew/cask/auditor.rb index caf2fa0ed8..0969a7dc30 100644 --- a/Library/Homebrew/cask/auditor.rb +++ b/Library/Homebrew/cask/auditor.rb @@ -57,8 +57,7 @@ module Cask if !language && language_blocks sample_languages = if language_blocks.length > LANGUAGE_BLOCK_LIMIT && !@audit_new_cask sample_keys = language_blocks.keys.sample(LANGUAGE_BLOCK_LIMIT) - sample_keys_sentence = ::Utils.to_sentence(sample_keys.map { |lang| lang[0].to_s }) - ohai "Auditing a sample of available languages: #{sample_keys_sentence}" + ohai "Auditing a sample of available languages: #{sample_keys.map { |lang| lang[0].to_s }.to_sentence}" language_blocks.select { |k| sample_keys.include?(k) } else language_blocks @@ -68,7 +67,7 @@ module Cask audit = audit_languages(l) summary = audit.summary(include_passed: output_passed?, include_warnings: output_warnings?) if summary.present? && output_summary?(audit) - ohai "Auditing language: #{::Utils.to_sentence(l.map { |lang| "'#{lang}'" })}" if output_summary? + ohai "Auditing language: #{l.map { |lang| "'#{lang}'" }.to_sentence}" if output_summary? puts summary end warnings += audit.warnings diff --git a/Library/Homebrew/cask/cmd/uninstall.rb b/Library/Homebrew/cask/cmd/uninstall.rb index e6874f2666..1700482a65 100644 --- a/Library/Homebrew/cask/cmd/uninstall.rb +++ b/Library/Homebrew/cask/cmd/uninstall.rb @@ -46,7 +46,7 @@ module Cask next if (versions = cask.versions).empty? puts <<~EOS - #{cask} #{::Utils.to_sentence(versions)} #{(versions.count == 1) ? "is" : "are"} still installed. + #{cask} #{versions.to_sentence} #{(versions.count == 1) ? "is" : "are"} still installed. Remove #{(versions.count == 1) ? "it" : "them all"} with `brew uninstall --cask --force #{cask}`. EOS end diff --git a/Library/Homebrew/cask/installer.rb b/Library/Homebrew/cask/installer.rb index 465cf18982..03e49a9744 100644 --- a/Library/Homebrew/cask/installer.rb +++ b/Library/Homebrew/cask/installer.rb @@ -306,7 +306,7 @@ module Cask rescue TSort::Cyclic strongly_connected_components = graph.strongly_connected_components.sort_by(&:count) cyclic_dependencies = strongly_connected_components.last - [@cask] - raise CaskCyclicDependencyError.new(@cask.token, ::Utils.to_sentence(cyclic_dependencies)) + raise CaskCyclicDependencyError.new(@cask.token, cyclic_dependencies.to_sentence) end end diff --git a/Library/Homebrew/cli/parser.rb b/Library/Homebrew/cli/parser.rb index 64d19f8ef2..568ab62d06 100644 --- a/Library/Homebrew/cli/parser.rb +++ b/Library/Homebrew/cli/parser.rb @@ -683,8 +683,8 @@ module Homebrew "This command does not take named arguments." else types << :named if types.empty? - arg_types = Utils.to_sentence(types.map { |type| type.to_s.tr("_", " ") }, - two_words_connector: " or ", last_word_connector: " or ") + arg_types = types.map { |type| type.to_s.tr("_", " ") } + .to_sentence two_words_connector: " or ", last_word_connector: " or " "This command does not take more than #{maximum} #{arg_types} #{Utils.pluralize("argument", maximum)}." end @@ -697,8 +697,8 @@ module Homebrew sig { params(minimum: Integer, types: T::Array[Symbol]).void } def initialize(minimum, types: []) types << :named if types.empty? - arg_types = Utils.to_sentence(types.map { |type| type.to_s.tr("_", " ") }, - two_words_connector: " or ", last_word_connector: " or ") + arg_types = types.map { |type| type.to_s.tr("_", " ") } + .to_sentence two_words_connector: " or ", last_word_connector: " or " super "This command requires at least #{minimum} #{arg_types} #{Utils.pluralize("argument", minimum)}." end @@ -710,8 +710,8 @@ module Homebrew sig { params(minimum: Integer, types: T::Array[Symbol]).void } def initialize(minimum, types: []) types << :named if types.empty? - arg_types = Utils.to_sentence(types.map { |type| type.to_s.tr("_", " ") }, - two_words_connector: " or ", last_word_connector: " or ") + arg_types = types.map { |type| type.to_s.tr("_", " ") } + .to_sentence two_words_connector: " or ", last_word_connector: " or " super "This command requires exactly #{minimum} #{arg_types} #{Utils.pluralize("argument", minimum)}." end diff --git a/Library/Homebrew/cmd/developer.rb b/Library/Homebrew/cmd/developer.rb index 028010707c..131f18cef5 100755 --- a/Library/Homebrew/cmd/developer.rb +++ b/Library/Homebrew/cmd/developer.rb @@ -40,8 +40,7 @@ module Homebrew case args.named.first when nil, "state" if env_vars.any? - env_vars_str = Utils.to_sentence(env_vars) - puts "Developer mode is enabled because #{env_vars_str} #{(env_vars.count == 1) ? "is" : "are"} set." + puts "Developer mode is enabled because #{env_vars.to_sentence} #{(env_vars.count == 1) ? "is" : "are"} set." elsif Homebrew::Settings.read("devcmdrun") == "true" puts "Developer mode is enabled." else @@ -51,7 +50,7 @@ module Homebrew Homebrew::Settings.write "devcmdrun", true when "off" Homebrew::Settings.delete "devcmdrun" - puts "To fully disable developer mode, you must unset #{Utils.to_sentence(env_vars)}." if env_vars.any? + puts "To fully disable developer mode, you must unset #{env_vars.to_sentence}." if env_vars.any? else raise UsageError, "unknown subcommand: #{args.named.first}" end diff --git a/Library/Homebrew/cmd/update-report.rb b/Library/Homebrew/cmd/update-report.rb index e75ede46ec..22306001bf 100644 --- a/Library/Homebrew/cmd/update-report.rb +++ b/Library/Homebrew/cmd/update-report.rb @@ -234,7 +234,7 @@ module Homebrew unless updated_taps.empty? auto_update_header args: args noun = Utils.pluralize("tap", updated_taps.count) - puts "Updated #{updated_taps.count} #{noun} (#{Utils.to_sentence(updated_taps)})." + puts "Updated #{updated_taps.count} #{noun} (#{updated_taps.to_sentence})." updated = true end diff --git a/Library/Homebrew/dev-cmd/audit.rb b/Library/Homebrew/dev-cmd/audit.rb index 40da9791ec..c5dac1cb53 100644 --- a/Library/Homebrew/dev-cmd/audit.rb +++ b/Library/Homebrew/dev-cmd/audit.rb @@ -286,7 +286,7 @@ module Homebrew error_sources << "#{cask_count} #{Utils.pluralize("cask", cask_count)}" if cask_count.positive? error_sources << "#{tap_count} #{Utils.pluralize("tap", tap_count)}" if tap_count.positive? - errors_summary += " in #{Utils.to_sentence(error_sources)}" if error_sources.any? + errors_summary += " in #{error_sources.to_sentence}" if error_sources.any? errors_summary += " detected" diff --git a/Library/Homebrew/dev-cmd/contributions.rb b/Library/Homebrew/dev-cmd/contributions.rb index 4129f41e43..9fe19f8b59 100755 --- a/Library/Homebrew/dev-cmd/contributions.rb +++ b/Library/Homebrew/dev-cmd/contributions.rb @@ -18,7 +18,6 @@ module Homebrew sig { returns(CLI::Parser) } def contributions_args - supported_repos_sentence = Utils.to_sentence(SUPPORTED_REPOS.map { |t| "`#{t}`" }) Homebrew::CLI::Parser.new do usage_banner "`contributions` [--user=] [<--repositories>`=`] [<--csv>]" description <<~EOS @@ -27,7 +26,7 @@ module Homebrew comma_array "--repositories", description: "Specify a comma-separated (no spaces) list of repositories to search. " \ - "Supported repositories: #{supported_repos_sentence}. " \ + "Supported repositories: #{SUPPORTED_REPOS.map { |t| "`#{t}`" }.to_sentence}. " \ "Omitting this flag, or specifying `--repositories=all`, searches all repositories. " \ "Use `--repositories=primary` to search only the main repositories: brew,core,cask." flag "--from=", diff --git a/Library/Homebrew/dev-cmd/update-maintainers.rb b/Library/Homebrew/dev-cmd/update-maintainers.rb index 6185a072c4..2b76426e55 100644 --- a/Library/Homebrew/dev-cmd/update-maintainers.rb +++ b/Library/Homebrew/dev-cmd/update-maintainers.rb @@ -37,7 +37,7 @@ module Homebrew members.each do |group, hash| hash.slice!(*public_members) hash.each { |login, name| hash[login] = "[#{name}](https://github.com/#{login})" } - sentences[group] = Utils.to_sentence(hash.values.sort) + sentences[group] = hash.values.sort.to_sentence end readme = HOMEBREW_REPOSITORY/"README.md" diff --git a/Library/Homebrew/dev-cmd/update-sponsors.rb b/Library/Homebrew/dev-cmd/update-sponsors.rb index 93879ce3ac..dc5b62b277 100644 --- a/Library/Homebrew/dev-cmd/update-sponsors.rb +++ b/Library/Homebrew/dev-cmd/update-sponsors.rb @@ -57,7 +57,7 @@ module Homebrew readme = HOMEBREW_REPOSITORY/"README.md" content = readme.read - content.gsub!(/(Homebrew is generously supported by) .*\Z/m, "\\1 #{Utils.to_sentence(named_sponsors)}.\n") + content.gsub!(/(Homebrew is generously supported by) .*\Z/m, "\\1 #{named_sponsors.to_sentence}.\n") content << "\n#{logo_sponsors.join}\n" if logo_sponsors.presence File.write(readme, content) diff --git a/Library/Homebrew/diagnostic.rb b/Library/Homebrew/diagnostic.rb index 4e12603528..3d1c512a22 100644 --- a/Library/Homebrew/diagnostic.rb +++ b/Library/Homebrew/diagnostic.rb @@ -911,7 +911,7 @@ module Homebrew end) taps = Utils.pluralize("tap", error_tap_paths.count) - "Unable to read from cask #{taps}: #{Utils.to_sentence(error_tap_paths)}" if error_tap_paths.present? + "Unable to read from cask #{taps}: #{error_tap_paths.to_sentence}" if error_tap_paths.present? end def check_cask_load_path diff --git a/Library/Homebrew/exceptions.rb b/Library/Homebrew/exceptions.rb index 56cb1da56b..7f8b4f9d6b 100644 --- a/Library/Homebrew/exceptions.rb +++ b/Library/Homebrew/exceptions.rb @@ -100,8 +100,7 @@ class FormulaOrCaskUnavailableError < RuntimeError similar_formula_names = Formula.fuzzy_search(name) return "" if similar_formula_names.blank? - "Did you mean #{Utils.to_sentence(similar_formula_names, two_words_connector: " or ", - last_word_connector: " or ")}?" + "Did you mean #{similar_formula_names.to_sentence two_words_connector: " or ", last_word_connector: " or "}?" end sig { returns(String) } @@ -571,7 +570,7 @@ class UnbottledError < RuntimeError msg = +<<~EOS The following #{Utils.pluralize("formula", formulae.count, plural: "e")} cannot be installed from #{Utils.pluralize("bottle", formulae.count)} and must be built from source. - #{Utils.to_sentence(formulae)} + #{formulae.to_sentence} EOS msg += "#{DevelopmentTools.installation_instructions}\n" unless DevelopmentTools.installed? msg.freeze @@ -805,8 +804,7 @@ class CyclicDependencyError < RuntimeError def initialize(strongly_connected_components) super <<~EOS The following packages contain cyclic dependencies: - #{strongly_connected_components.select { |packages| packages.count > 1 } - .map { |p| Utils.to_sentence(p) }.join("\n ")} + #{strongly_connected_components.select { |packages| packages.count > 1 }.map(&:to_sentence).join("\n ")} EOS end end diff --git a/Library/Homebrew/formula_installer.rb b/Library/Homebrew/formula_installer.rb index 1af0800d3b..12eb8ec499 100644 --- a/Library/Homebrew/formula_installer.rb +++ b/Library/Homebrew/formula_installer.rb @@ -652,7 +652,7 @@ class FormulaInstaller puts "All dependencies for #{formula.full_name} are satisfied." elsif !deps.empty? oh1 "Installing dependencies for #{formula.full_name}: " \ - "#{Utils.to_sentence(deps.map(&:first).map(&Formatter.method(:identifier)))}", + "#{deps.map(&:first).map(&Formatter.method(:identifier)).to_sentence}", truncate: false deps.each { |dep, options| install_dependency(dep, options) } end @@ -1163,7 +1163,7 @@ class FormulaInstaller return if deps.empty? oh1 "Fetching dependencies for #{formula.full_name}: " \ - "#{Utils.to_sentence(deps.map(&:first).map(&Formatter.method(:identifier)))}", + "#{deps.map(&:first).map(&Formatter.method(:identifier)).to_sentence}", truncate: false deps.each { |dep, _options| fetch_dependency(dep) } diff --git a/Library/Homebrew/rubocops/all.rb b/Library/Homebrew/rubocops/all.rb index 96a9f5176c..ba9442d61c 100644 --- a/Library/Homebrew/rubocops/all.rb +++ b/Library/Homebrew/rubocops/all.rb @@ -1,6 +1,9 @@ # typed: strict # frozen_string_literal: true +# TODO: remove this (and avoid further active support in rubocops) +require "active_support/core_ext/array/conversions" + require_relative "io_read" require_relative "move_to_extend_os" require_relative "shell_commands" diff --git a/Library/Homebrew/rubocops/components_order.rb b/Library/Homebrew/rubocops/components_order.rb index 96204a58e0..4661aed6fa 100644 --- a/Library/Homebrew/rubocops/components_order.rb +++ b/Library/Homebrew/rubocops/components_order.rb @@ -3,7 +3,6 @@ require "ast_constants" require "rubocops/extend/formula_cop" -require "utils/array" module RuboCop module Cop @@ -76,8 +75,8 @@ module RuboCop [:url, :version, :sha256], [:url, :mirror, :version, :sha256], ] - minimum_methods = Utils.to_sentence(allowed_methods.first.map { |m| "`#{m}`" }) - maximum_methods = Utils.to_sentence(allowed_methods.last.map { |m| "`#{m}`" }) + minimum_methods = allowed_methods.first.map { |m| "`#{m}`" }.to_sentence + maximum_methods = allowed_methods.last.map { |m| "`#{m}`" }.to_sentence on_system_bodies.each do |on_system_block, on_system_body| method_name = on_system_block.method_name diff --git a/Library/Homebrew/tap.rb b/Library/Homebrew/tap.rb index 21bad4bf22..457fe67726 100644 --- a/Library/Homebrew/tap.rb +++ b/Library/Homebrew/tap.rb @@ -326,7 +326,7 @@ class Tap Commands.rebuild_commands_completion_list link_completions_and_manpages - formatted_contents = contents.presence&.then { |c| Utils.to_sentence(c) }&.dup&.prepend(" ") + formatted_contents = contents.presence&.to_sentence&.dup&.prepend(" ") $stderr.puts "Tapped#{formatted_contents} (#{path.abv})." unless quiet CacheStoreDatabase.use(:descriptions) do |db| DescriptionCacheStore.new(db) @@ -414,7 +414,7 @@ class Tap $stderr.puts "Untapping #{name}..." abv = path.abv - formatted_contents = contents.presence&.then { |c| Utils.to_sentence(c) }&.dup&.prepend(" ") + formatted_contents = contents.presence&.to_sentence&.dup&.prepend(" ") CacheStoreDatabase.use(:descriptions) do |db| DescriptionCacheStore.new(db) diff --git a/Library/Homebrew/uninstall.rb b/Library/Homebrew/uninstall.rb index 4e7c9eff8f..a6f366f448 100644 --- a/Library/Homebrew/uninstall.rb +++ b/Library/Homebrew/uninstall.rb @@ -52,7 +52,7 @@ module Homebrew if rack.directory? versions = rack.subdirs.map(&:basename) puts <<~EOS - #{keg.name} #{Utils.to_sentence(versions)} #{(versions.count == 1) ? "is" : "are"} still installed. + #{keg.name} #{versions.to_sentence} #{(versions.count == 1) ? "is" : "are"} still installed. To remove all versions, run: brew uninstall --force #{keg.name} EOS @@ -136,7 +136,7 @@ module Homebrew end def are_required_by_deps - "#{(reqs.count == 1) ? "is" : "are"} required by #{Utils.to_sentence(deps)}, " \ + "#{(reqs.count == 1) ? "is" : "are"} required by #{deps.to_sentence}, " \ "which #{(deps.count == 1) ? "is" : "are"} currently installed" end end @@ -145,7 +145,7 @@ module Homebrew class DeveloperDependentsMessage < DependentsMessage def output opoo <<~EOS - #{Utils.to_sentence(reqs)} #{are_required_by_deps}. + #{reqs.to_sentence} #{are_required_by_deps}. You can silence this warning with: #{sample_command} EOS @@ -156,7 +156,7 @@ module Homebrew class NondeveloperDependentsMessage < DependentsMessage def output ofail <<~EOS - Refusing to uninstall #{Utils.to_sentence(reqs)} + Refusing to uninstall #{reqs.to_sentence} because #{(reqs.count == 1) ? "it" : "they"} #{are_required_by_deps}. You can override this and force removal with: #{sample_command} diff --git a/Library/Homebrew/utils.rb b/Library/Homebrew/utils.rb index f231d698f5..3ca0e63535 100644 --- a/Library/Homebrew/utils.rb +++ b/Library/Homebrew/utils.rb @@ -4,7 +4,6 @@ require "time" require "utils/analytics" -require "utils/array" require "utils/curl" require "utils/fork" require "utils/formatter" diff --git a/Library/Homebrew/utils/array.rb b/Library/Homebrew/utils/array.rb deleted file mode 100644 index 4b95bcf484..0000000000 --- a/Library/Homebrew/utils/array.rb +++ /dev/null @@ -1,53 +0,0 @@ -# typed: strict -# frozen_string_literal: true - -module Utils - extend T::Sig - - # Converts the array to a comma-separated sentence where the last element is - # joined by the connector word. - # - # You can pass the following options to change the default behavior. If you - # pass an option key that doesn't exist in the list below, it will raise an - # ArgumentError. - # - # ==== Options - # - # * :words_connector - The sign or word used to join all but the last - # element in arrays with three or more elements (default: ", "). - # * :last_word_connector - The sign or word used to join the last element - # in arrays with three or more elements (default: ", and "). - # * :two_words_connector - The sign or word used to join the elements - # in arrays with two elements (default: " and "). - # - # ==== Examples - # - # [].to_sentence # => "" - # ['one'].to_sentence # => "one" - # ['one', 'two'].to_sentence # => "one and two" - # ['one', 'two', 'three'].to_sentence # => "one, two, and three" - # - # ['one', 'two'].to_sentence(two_words_connector: '-') - # # => "one-two" - # - # ['one', 'two', 'three'].to_sentence(words_connector: ' or ', last_word_connector: ' or at least ') - # # => "one or two or at least three" - # @see https://github.com/rails/rails/blob/v6.1.7.2/activesupport/lib/active_support/core_ext/array/conversions.rb#L10-L85 - # ActiveSupport implementation - sig { - params(array: T::Array[String], words_connector: String, two_words_connector: String, last_word_connector: String) - .returns(String) - } - def self.to_sentence(array, words_connector: ", ", two_words_connector: " and ", last_word_connector: ", and ") - case array.length - when 0 - +"" - when 1 - +(array[0]).to_s - when 2 - +"#{array[0]}#{two_words_connector}#{array[1]}" - else - +"#{T.must(array[0...-1]).join(words_connector)}#{last_word_connector}#{array[-1]}" - end - end -end From e68b02c4a6c7b85be208cdac8bb1f4299e72d3e9 Mon Sep 17 00:00:00 2001 From: Douglas Eichelberger Date: Thu, 2 Mar 2023 14:26:47 -0800 Subject: [PATCH 06/10] Move tests and requires --- Library/Homebrew/extend/array.rb | 42 ++++++++++++++++++ Library/Homebrew/global.rb | 1 + Library/Homebrew/rubocops/all.rb | 4 +- Library/Homebrew/test/extend/array_spec.rb | 51 ++++++++++++++++++++++ Library/Homebrew/test/utils_spec.rb | 47 -------------------- 5 files changed, 95 insertions(+), 50 deletions(-) create mode 100644 Library/Homebrew/extend/array.rb create mode 100644 Library/Homebrew/test/extend/array_spec.rb diff --git a/Library/Homebrew/extend/array.rb b/Library/Homebrew/extend/array.rb new file mode 100644 index 0000000000..1d91c99b52 --- /dev/null +++ b/Library/Homebrew/extend/array.rb @@ -0,0 +1,42 @@ +# typed: true +# frozen_string_literal: true + +class Array + # Converts the array to a comma-separated sentence where the last element is + # joined by the connector word. + # + # You can pass the following kwargs to change the default behavior: + # + # * :words_connector - The sign or word used to join all but the last + # element in arrays with three or more elements (default: ", "). + # * :last_word_connector - The sign or word used to join the last element + # in arrays with three or more elements (default: ", and "). + # * :two_words_connector - The sign or word used to join the elements + # in arrays with two elements (default: " and "). + # + # ==== Examples + # + # [].to_sentence # => "" + # ['one'].to_sentence # => "one" + # ['one', 'two'].to_sentence # => "one and two" + # ['one', 'two', 'three'].to_sentence # => "one, two, and three" + # ['one', 'two'].to_sentence(two_words_connector: '-') + # # => "one-two" + # + # ['one', 'two', 'three'].to_sentence(words_connector: ' or ', last_word_connector: ' or at least ') + # # => "one or two or at least three" + # @see https://github.com/rails/rails/blob/v7.0.4.2/activesupport/lib/active_support/core_ext/array/conversions.rb#L8-L84 + # ActiveSupport Array#to_sentence monkey-patch + def to_sentence(words_connector: ", ", two_words_connector: " and ", last_word_connector: ", and ") + case length + when 0 + +"" + when 1 + +(self[0]).to_s + when 2 + +"#{self[0]}#{two_words_connector}#{self[1]}" + else + +"#{self[0...-1].join(words_connector)}#{last_word_connector}#{self[-1]}" + end + end +end diff --git a/Library/Homebrew/global.rb b/Library/Homebrew/global.rb index fb3d785efa..9666251ced 100644 --- a/Library/Homebrew/global.rb +++ b/Library/Homebrew/global.rb @@ -133,6 +133,7 @@ module Homebrew end require "context" +require "extend/array" require "extend/git_repository" require "extend/pathname" require "extend/predicable" diff --git a/Library/Homebrew/rubocops/all.rb b/Library/Homebrew/rubocops/all.rb index ba9442d61c..ec0fe92957 100644 --- a/Library/Homebrew/rubocops/all.rb +++ b/Library/Homebrew/rubocops/all.rb @@ -1,9 +1,7 @@ # typed: strict # frozen_string_literal: true -# TODO: remove this (and avoid further active support in rubocops) -require "active_support/core_ext/array/conversions" - +require_relative "../extend/array" require_relative "io_read" require_relative "move_to_extend_os" require_relative "shell_commands" diff --git a/Library/Homebrew/test/extend/array_spec.rb b/Library/Homebrew/test/extend/array_spec.rb new file mode 100644 index 0000000000..6654b23a69 --- /dev/null +++ b/Library/Homebrew/test/extend/array_spec.rb @@ -0,0 +1,51 @@ +# typed: false +# frozen_string_literal: true + +require "extend/array" + +describe Array do + describe ".to_sentence" do + it "converts a plain array to a sentence" do + expect([].to_sentence).to eq("") + expect(["one"].to_sentence).to eq("one") + expect(["one", "two"].to_sentence).to eq("one and two") + expect(["one", "two", "three"].to_sentence).to eq("one, two, and three") + end + + it "converts an array to a sentence with a custom connector" do + expect(["one", "two", "three"].to_sentence(words_connector: " ")).to eq("one two, and three") + expect(["one", "two", "three"].to_sentence(words_connector: " & ")).to eq("one & two, and three") + end + + it "converts an array to a sentence with a custom last word connector" do + expect(["one", "two", "three"].to_sentence(last_word_connector: ", and also ")) + .to eq("one, two, and also three") + expect(["one", "two", "three"].to_sentence(last_word_connector: " ")).to eq("one, two three") + expect(["one", "two", "three"].to_sentence(last_word_connector: " and ")).to eq("one, two and three") + end + + it "converts an array to a sentence with a custom two word connector" do + expect(["one", "two"].to_sentence(two_words_connector: " ")).to eq("one two") + end + + it "creates a new string" do + elements = ["one"] + expect(elements.to_sentence.object_id).not_to eq(elements[0].object_id) + end + + it "converts a non-String to a sentence" do + expect([1].to_sentence).to eq("1") + end + + it "converts an array with blank elements to a sentence" do + expect([nil, "one", "", "two", "three"].to_sentence).to eq(", one, , two, and three") + end + + it "does not return a frozen string" do + expect([""].to_sentence).not_to be_frozen + expect(["one"].to_sentence).not_to be_frozen + expect(["one", "two"].to_sentence).not_to be_frozen + expect(["one", "two", "three"].to_sentence).not_to be_frozen + end + end +end diff --git a/Library/Homebrew/test/utils_spec.rb b/Library/Homebrew/test/utils_spec.rb index 551f7d9c78..c75d21bed8 100644 --- a/Library/Homebrew/test/utils_spec.rb +++ b/Library/Homebrew/test/utils_spec.rb @@ -58,51 +58,4 @@ describe Utils do expect(described_class.pluralize("foo", 2, singular: "o", plural: "es")).to eq("fooes") end end - - describe ".to_sentence" do - it "converts a plain array to a sentence" do - expect(described_class.to_sentence([])).to eq("") - expect(described_class.to_sentence(["one"])).to eq("one") - expect(described_class.to_sentence(["one", "two"])).to eq("one and two") - expect(described_class.to_sentence(["one", "two", "three"])).to eq("one, two, and three") - end - - it "converts an array to a sentence with a custom connector" do - expect(described_class.to_sentence(["one", "two", "three"], words_connector: " ")).to eq("one two, and three") - expect(described_class.to_sentence(["one", "two", "three"], - words_connector: " & ")).to eq("one & two, and three") - end - - it "converts an array to a sentence with a custom last word connector" do - expect(described_class.to_sentence(["one", "two", "three"], - last_word_connector: ", and also ")).to eq("one, two, and also three") - expect(described_class.to_sentence(["one", "two", "three"], last_word_connector: " ")).to eq("one, two three") - expect(described_class.to_sentence(["one", "two", "three"], - last_word_connector: " and ")).to eq("one, two and three") - end - - it "converts an array to a sentence with a custom two word connector" do - expect(described_class.to_sentence(["one", "two"], two_words_connector: " ")).to eq("one two") - end - - it "creates a new string" do - elements = ["one"] - expect(described_class.to_sentence(elements).object_id).not_to eq(elements[0].object_id) - end - - it "converts a non-String to a sentence" do - expect(described_class.to_sentence([1])).to eq("1") - end - - it "converts an array with blank elements to a sentence" do - expect(described_class.to_sentence([nil, "one", "", "two", "three"])).to eq(", one, , two, and three") - end - - it "does not return a frozen string" do - expect(described_class.to_sentence([])).not_to be_frozen - expect(described_class.to_sentence(["one"])).not_to be_frozen - expect(described_class.to_sentence(["one", "two"])).not_to be_frozen - expect(described_class.to_sentence(["one", "two", "three"])).not_to be_frozen - end - end end From d8ba8c807ae1f433dd6135c253cac7fdeeea774f Mon Sep 17 00:00:00 2001 From: Douglas Eichelberger Date: Thu, 2 Mar 2023 14:39:55 -0800 Subject: [PATCH 07/10] Fix type errors --- Library/Homebrew/extend/array.rb | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Library/Homebrew/extend/array.rb b/Library/Homebrew/extend/array.rb index 1d91c99b52..21b282a6f5 100644 --- a/Library/Homebrew/extend/array.rb +++ b/Library/Homebrew/extend/array.rb @@ -32,11 +32,12 @@ class Array when 0 +"" when 1 - +(self[0]).to_s + # This is not typesafe, if the array contains a BasicObject + +T.unsafe(self[0]).to_s when 2 +"#{self[0]}#{two_words_connector}#{self[1]}" else - +"#{self[0...-1].join(words_connector)}#{last_word_connector}#{self[-1]}" + +"#{T.must(self[0...-1]).join(words_connector)}#{last_word_connector}#{self[-1]}" end end end From 317874c39442cbe335c8d4e74aec864f88eaeaf1 Mon Sep 17 00:00:00 2001 From: Douglas Eichelberger Date: Tue, 28 Feb 2023 18:03:42 -0800 Subject: [PATCH 08/10] Fix type error --- Library/Homebrew/rubocops/components_order.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Library/Homebrew/rubocops/components_order.rb b/Library/Homebrew/rubocops/components_order.rb index 4661aed6fa..dbc8463a02 100644 --- a/Library/Homebrew/rubocops/components_order.rb +++ b/Library/Homebrew/rubocops/components_order.rb @@ -75,8 +75,8 @@ module RuboCop [:url, :version, :sha256], [:url, :mirror, :version, :sha256], ] - minimum_methods = allowed_methods.first.map { |m| "`#{m}`" }.to_sentence - maximum_methods = allowed_methods.last.map { |m| "`#{m}`" }.to_sentence + minimum_methods = ::Utils.to_sentence(allowed_methods.first.map { |m| "`#{m}`" }) + maximum_methods = ::Utils.to_sentence(allowed_methods.last.map { |m| "`#{m}`" }) on_system_bodies.each do |on_system_block, on_system_body| method_name = on_system_block.method_name From e94d3767e63bd697a95396dcf4094a964b7f4ceb Mon Sep 17 00:00:00 2001 From: Douglas Eichelberger Date: Fri, 3 Mar 2023 12:43:15 -0800 Subject: [PATCH 09/10] Add rails license --- Library/Homebrew/extend/array.rb | 21 +++++++++++++++++++ Library/Homebrew/rubocops/components_order.rb | 4 ++-- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/Library/Homebrew/extend/array.rb b/Library/Homebrew/extend/array.rb index 21b282a6f5..2c2d1dfa71 100644 --- a/Library/Homebrew/extend/array.rb +++ b/Library/Homebrew/extend/array.rb @@ -27,6 +27,27 @@ class Array # # => "one or two or at least three" # @see https://github.com/rails/rails/blob/v7.0.4.2/activesupport/lib/active_support/core_ext/array/conversions.rb#L8-L84 # ActiveSupport Array#to_sentence monkey-patch + # + # Copyright (c) David Heinemeier Hansson + # + # Permission is hereby granted, free of charge, to any person obtaining + # a copy of this software and associated documentation files (the + # "Software"), to deal in the Software without restriction, including + # without limitation the rights to use, copy, modify, merge, publish, + # distribute, sublicense, and/or sell copies of the Software, and to + # permit persons to whom the Software is furnished to do so, subject to + # the following conditions: + # + # The above copyright notice and this permission notice shall be + # included in all copies or substantial portions of the Software. + # + # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. def to_sentence(words_connector: ", ", two_words_connector: " and ", last_word_connector: ", and ") case length when 0 diff --git a/Library/Homebrew/rubocops/components_order.rb b/Library/Homebrew/rubocops/components_order.rb index dbc8463a02..4661aed6fa 100644 --- a/Library/Homebrew/rubocops/components_order.rb +++ b/Library/Homebrew/rubocops/components_order.rb @@ -75,8 +75,8 @@ module RuboCop [:url, :version, :sha256], [:url, :mirror, :version, :sha256], ] - minimum_methods = ::Utils.to_sentence(allowed_methods.first.map { |m| "`#{m}`" }) - maximum_methods = ::Utils.to_sentence(allowed_methods.last.map { |m| "`#{m}`" }) + minimum_methods = allowed_methods.first.map { |m| "`#{m}`" }.to_sentence + maximum_methods = allowed_methods.last.map { |m| "`#{m}`" }.to_sentence on_system_bodies.each do |on_system_block, on_system_body| method_name = on_system_block.method_name From 8527b01b2b0dc17ee25d3048ef20170aea6755a6 Mon Sep 17 00:00:00 2001 From: Douglas Eichelberger Date: Fri, 3 Mar 2023 18:07:39 -0800 Subject: [PATCH 10/10] Oxford commas --- Library/Homebrew/test/rubocops/components_order_spec.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Library/Homebrew/test/rubocops/components_order_spec.rb b/Library/Homebrew/test/rubocops/components_order_spec.rb index afead946d0..cc1dd2f3a1 100644 --- a/Library/Homebrew/test/rubocops/components_order_spec.rb +++ b/Library/Homebrew/test/rubocops/components_order_spec.rb @@ -1014,7 +1014,7 @@ describe RuboCop::Cop::FormulaAudit::ComponentsOrder do resource do on_macos do - ^^^^^^^^^^^ `on_macos` blocks within `resource` blocks must contain at least `url` and `sha256` and at most `url`, `mirror`, `version` and `sha256` (in order). + ^^^^^^^^^^^ `on_macos` blocks within `resource` blocks must contain at least `url` and `sha256` and at most `url`, `mirror`, `version`, and `sha256` (in order). sha256 "586372eb92059873e29eba4f9dec8381541b4d3834660707faf8ba59146dfc35" url "https://brew.sh/resource2.tar.gz" end @@ -1081,7 +1081,7 @@ describe RuboCop::Cop::FormulaAudit::ComponentsOrder do resource do on_macos do - ^^^^^^^^^^^ `on_macos` blocks within `resource` blocks must contain at least `url` and `sha256` and at most `url`, `mirror`, `version` and `sha256` (in order). + ^^^^^^^^^^^ `on_macos` blocks within `resource` blocks must contain at least `url` and `sha256` and at most `url`, `mirror`, `version`, and `sha256` (in order). if foo == :bar url "https://brew.sh/resource2.tar.gz" sha256 "586372eb92059873e29eba4f9dec8381541b4d3834660707faf8ba59146dfc35" @@ -1112,7 +1112,7 @@ describe RuboCop::Cop::FormulaAudit::ComponentsOrder do end on_arm do - ^^^^^^^^^ `on_arm` blocks within `resource` blocks must contain at least `url` and `sha256` and at most `url`, `mirror`, `version` and `sha256` (in order). + ^^^^^^^^^ `on_arm` blocks within `resource` blocks must contain at least `url` and `sha256` and at most `url`, `mirror`, `version`, and `sha256` (in order). sha256 "586372eb92059873e29eba4f9dec8381541b4d3834660707faf8ba59146dfc35" url "https://brew.sh/resource2.tar.gz" end @@ -1158,7 +1158,7 @@ describe RuboCop::Cop::FormulaAudit::ComponentsOrder do end on_arm do - ^^^^^^^^^ `on_arm` blocks within `resource` blocks must contain at least `url` and `sha256` and at most `url`, `mirror`, `version` and `sha256` (in order). + ^^^^^^^^^ `on_arm` blocks within `resource` blocks must contain at least `url` and `sha256` and at most `url`, `mirror`, `version`, and `sha256` (in order). if foo == :bar url "https://brew.sh/resource2.tar.gz" sha256 "586372eb92059873e29eba4f9dec8381541b4d3834660707faf8ba59146dfc35"