From 7aacda175465d105d1db07e2268782e72a9cbf99 Mon Sep 17 00:00:00 2001 From: Alexander Bayandin Date: Sat, 19 Jun 2021 00:14:33 +0100 Subject: [PATCH 1/6] formula: add fuzzy_search method --- Library/Homebrew/brew.sh | 9 +++------ Library/Homebrew/formula.rb | 8 ++++++++ Library/Homebrew/tap.rb | 1 + 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/Library/Homebrew/brew.sh b/Library/Homebrew/brew.sh index e2875794a7..cb69767ebb 100644 --- a/Library/Homebrew/brew.sh +++ b/Library/Homebrew/brew.sh @@ -583,14 +583,11 @@ then # Don't allow non-developers to customise Ruby warnings. unset HOMEBREW_RUBY_WARNINGS - - # Disable Ruby options we don't need. - RUBY_DISABLE_OPTIONS="--disable=did_you_mean,rubyopt" -else - # Don't disable did_you_mean for developers as it's useful. - RUBY_DISABLE_OPTIONS="--disable=rubyopt" fi +# Disable Ruby options we don't need. +RUBY_DISABLE_OPTIONS="--disable=rubyopt" + if [[ -z "${HOMEBREW_RUBY_WARNINGS}" ]] then export HOMEBREW_RUBY_WARNINGS="-W1" diff --git a/Library/Homebrew/formula.rb b/Library/Homebrew/formula.rb index a1806c4b24..7d1f18d03a 100644 --- a/Library/Homebrew/formula.rb +++ b/Library/Homebrew/formula.rb @@ -2,6 +2,7 @@ # frozen_string_literal: true require "cache_store" +require "did_you_mean" require "formula_support" require "lock_file" require "formula_pin" @@ -1671,6 +1672,13 @@ class Formula CoreTap.instance.alias_reverse_table end + # Returns a list of approximately matching formula names, but not the complete match + # @private + def self.fuzzy_search(name) + @spell_checker ||= DidYouMean::SpellChecker.new(dictionary: Set.new(names + full_names).to_a) + @spell_checker.correct(name) + end + def self.[](name) Formulary.factory(name) end diff --git a/Library/Homebrew/tap.rb b/Library/Homebrew/tap.rb index 99d0081059..810a9ea474 100644 --- a/Library/Homebrew/tap.rb +++ b/Library/Homebrew/tap.rb @@ -127,6 +127,7 @@ class Tap @style_exceptions = nil @pypi_formula_mappings = nil @config = nil + @spell_checker = nil remove_instance_variable(:@private) if instance_variable_defined?(:@private) end From ce4410fd8d95dc8809e2bb57e0e29311beac112f Mon Sep 17 00:00:00 2001 From: Alexander Bayandin Date: Sat, 19 Jun 2021 00:18:51 +0100 Subject: [PATCH 2/6] exceptions: add "Did you mean ..." for FormulaOrCaskUnavailableError --- Library/Homebrew/exceptions.rb | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/Library/Homebrew/exceptions.rb b/Library/Homebrew/exceptions.rb index 09765e0068..a0db4eb9b2 100644 --- a/Library/Homebrew/exceptions.rb +++ b/Library/Homebrew/exceptions.rb @@ -91,9 +91,17 @@ class FormulaOrCaskUnavailableError < RuntimeError @name = name end + sig { returns(String) } + def did_you_mean + 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 "}?" + end + sig { returns(String) } def to_s - "No available formula or cask with the name \"#{name}\"." + "No available formula or cask with the name \"#{name}\". #{did_you_mean}".strip end end @@ -129,7 +137,7 @@ class FormulaUnavailableError < FormulaOrCaskUnavailableError sig { returns(String) } def to_s - "No available formula with the name \"#{name}\"#{dependent_s}." + "No available formula with the name \"#{name}\"#{dependent_s}. #{did_you_mean}".strip end end From d455ca937784aa4e1b282579064202cafb780c34 Mon Sep 17 00:00:00 2001 From: Alexander Bayandin Date: Sat, 19 Jun 2021 00:19:24 +0100 Subject: [PATCH 3/6] search: add fuzzy search results --- Library/Homebrew/search.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Library/Homebrew/search.rb b/Library/Homebrew/search.rb index 773579de0b..fc73988fdd 100644 --- a/Library/Homebrew/search.rb +++ b/Library/Homebrew/search.rb @@ -87,6 +87,8 @@ module Homebrew .search(string_or_regex) .sort + results += Formula.fuzzy_search(string_or_regex) + results.map do |name| formula, canonical_full_name = begin f = Formulary.factory(name) From 4c57b9d8f84746a362bb46be7b74b6113e319756 Mon Sep 17 00:00:00 2001 From: Alexander Bayandin Date: Tue, 22 Jun 2021 12:30:20 +0100 Subject: [PATCH 4/6] Gemfile: add did_you_mean gem --- Library/Homebrew/Gemfile | 1 + Library/Homebrew/Gemfile.lock | 2 ++ 2 files changed, 3 insertions(+) diff --git a/Library/Homebrew/Gemfile b/Library/Homebrew/Gemfile index 70c9b44ba2..8f931184bd 100644 --- a/Library/Homebrew/Gemfile +++ b/Library/Homebrew/Gemfile @@ -30,6 +30,7 @@ end # vendored gems gem "activesupport" gem "concurrent-ruby" +gem "did_you_mean" # remove when HOMEBREW_REQUIRED_RUBY_VERSION >= 2.7 gem "mechanize" gem "patchelf" gem "plist" diff --git a/Library/Homebrew/Gemfile.lock b/Library/Homebrew/Gemfile.lock index 6bfd4b1237..3960af521f 100644 --- a/Library/Homebrew/Gemfile.lock +++ b/Library/Homebrew/Gemfile.lock @@ -20,6 +20,7 @@ GEM highline (~> 2.0.0) concurrent-ruby (1.1.9) connection_pool (2.2.5) + did_you_mean (1.5.0) diff-lcs (1.4.4) docile (1.4.0) domain_name (0.5.20190701) @@ -181,6 +182,7 @@ DEPENDENCIES bootsnap byebug concurrent-ruby + did_you_mean mechanize minitest nokogiri From 4a81d10161a48d540cad835d293560588b8fe7a8 Mon Sep 17 00:00:00 2001 From: Alexander Bayandin Date: Tue, 22 Jun 2021 12:34:29 +0100 Subject: [PATCH 5/6] brew vendor-gems: commit updates. --- .../Homebrew/vendor/bundle/bundler/setup.rb | 1 + .../did_you_mean-1.5.0/lib/did_you_mean.rb | 112 ++++++++++++++++++ .../lib/did_you_mean/core_ext/name_error.rb | 25 ++++ .../lib/did_you_mean/experimental.rb | 2 + .../formatters/plain_formatter.rb | 33 ++++++ .../formatters/verbose_formatter.rb | 49 ++++++++ .../lib/did_you_mean/jaro_winkler.rb | 87 ++++++++++++++ .../lib/did_you_mean/levenshtein.rb | 57 +++++++++ .../lib/did_you_mean/spell_checker.rb | 46 +++++++ .../spell_checkers/key_error_checker.rb | 20 ++++ .../spell_checkers/method_name_checker.rb | 69 +++++++++++ .../spell_checkers/name_error_checkers.rb | 20 ++++ .../name_error_checkers/class_name_checker.rb | 49 ++++++++ .../variable_name_checker.rb | 82 +++++++++++++ .../spell_checkers/null_checker.rb | 6 + .../spell_checkers/require_path_checker.rb | 35 ++++++ .../lib/did_you_mean/tree_spell_checker.rb | 109 +++++++++++++++++ .../lib/did_you_mean/verbose.rb | 4 + .../lib/did_you_mean/version.rb | 3 + 19 files changed, 809 insertions(+) create mode 100644 Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/did_you_mean-1.5.0/lib/did_you_mean.rb create mode 100644 Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/did_you_mean-1.5.0/lib/did_you_mean/core_ext/name_error.rb create mode 100644 Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/did_you_mean-1.5.0/lib/did_you_mean/experimental.rb create mode 100644 Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/did_you_mean-1.5.0/lib/did_you_mean/formatters/plain_formatter.rb create mode 100644 Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/did_you_mean-1.5.0/lib/did_you_mean/formatters/verbose_formatter.rb create mode 100644 Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/did_you_mean-1.5.0/lib/did_you_mean/jaro_winkler.rb create mode 100644 Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/did_you_mean-1.5.0/lib/did_you_mean/levenshtein.rb create mode 100644 Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/did_you_mean-1.5.0/lib/did_you_mean/spell_checker.rb create mode 100644 Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/did_you_mean-1.5.0/lib/did_you_mean/spell_checkers/key_error_checker.rb create mode 100644 Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/did_you_mean-1.5.0/lib/did_you_mean/spell_checkers/method_name_checker.rb create mode 100644 Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/did_you_mean-1.5.0/lib/did_you_mean/spell_checkers/name_error_checkers.rb create mode 100644 Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/did_you_mean-1.5.0/lib/did_you_mean/spell_checkers/name_error_checkers/class_name_checker.rb create mode 100644 Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/did_you_mean-1.5.0/lib/did_you_mean/spell_checkers/name_error_checkers/variable_name_checker.rb create mode 100644 Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/did_you_mean-1.5.0/lib/did_you_mean/spell_checkers/null_checker.rb create mode 100644 Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/did_you_mean-1.5.0/lib/did_you_mean/spell_checkers/require_path_checker.rb create mode 100644 Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/did_you_mean-1.5.0/lib/did_you_mean/tree_spell_checker.rb create mode 100644 Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/did_you_mean-1.5.0/lib/did_you_mean/verbose.rb create mode 100644 Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/did_you_mean-1.5.0/lib/did_you_mean/version.rb diff --git a/Library/Homebrew/vendor/bundle/bundler/setup.rb b/Library/Homebrew/vendor/bundle/bundler/setup.rb index f333d921be..c7dee16ff2 100644 --- a/Library/Homebrew/vendor/bundle/bundler/setup.rb +++ b/Library/Homebrew/vendor/bundle/bundler/setup.rb @@ -25,6 +25,7 @@ $:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/colorize-0.8.1/lib" $:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/highline-2.0.3/lib" $:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/commander-4.6.0/lib" $:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/connection_pool-2.2.5/lib" +$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/did_you_mean-1.5.0/lib" $:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/diff-lcs-1.4.4/lib" $:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/docile-1.4.0/lib" $:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/extensions/x86_64-darwin-14/2.6.0-static/unf_ext-0.0.7.7" diff --git a/Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/did_you_mean-1.5.0/lib/did_you_mean.rb b/Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/did_you_mean-1.5.0/lib/did_you_mean.rb new file mode 100644 index 0000000000..ab7e6b01a8 --- /dev/null +++ b/Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/did_you_mean-1.5.0/lib/did_you_mean.rb @@ -0,0 +1,112 @@ +require_relative "did_you_mean/version" +require_relative "did_you_mean/core_ext/name_error" + +require_relative "did_you_mean/spell_checker" +require_relative 'did_you_mean/spell_checkers/name_error_checkers' +require_relative 'did_you_mean/spell_checkers/method_name_checker' +require_relative 'did_you_mean/spell_checkers/key_error_checker' +require_relative 'did_you_mean/spell_checkers/null_checker' +require_relative 'did_you_mean/spell_checkers/require_path_checker' +require_relative 'did_you_mean/formatters/plain_formatter' +require_relative 'did_you_mean/tree_spell_checker' + +# The +DidYouMean+ gem adds functionality to suggest possible method/class +# names upon errors such as +NameError+ and +NoMethodError+. In Ruby 2.3 or +# later, it is automatically activated during startup. +# +# @example +# +# methosd +# # => NameError: undefined local variable or method `methosd' for main:Object +# # Did you mean? methods +# # method +# +# OBject +# # => NameError: uninitialized constant OBject +# # Did you mean? Object +# +# @full_name = "Yuki Nishijima" +# first_name, last_name = full_name.split(" ") +# # => NameError: undefined local variable or method `full_name' for main:Object +# # Did you mean? @full_name +# +# @@full_name = "Yuki Nishijima" +# @@full_anme +# # => NameError: uninitialized class variable @@full_anme in Object +# # Did you mean? @@full_name +# +# full_name = "Yuki Nishijima" +# full_name.starts_with?("Y") +# # => NoMethodError: undefined method `starts_with?' for "Yuki Nishijima":String +# # Did you mean? start_with? +# +# hash = {foo: 1, bar: 2, baz: 3} +# hash.fetch(:fooo) +# # => KeyError: key not found: :fooo +# # Did you mean? :foo +# +# +# == Disabling +did_you_mean+ +# +# Occasionally, you may want to disable the +did_you_mean+ gem for e.g. +# debugging issues in the error object itself. You can disable it entirely by +# specifying +--disable-did_you_mean+ option to the +ruby+ command: +# +# $ ruby --disable-did_you_mean -e "1.zeor?" +# -e:1:in `
': undefined method `zeor?' for 1:Integer (NameError) +# +# When you do not have direct access to the +ruby+ command (e.g. +# +rails console+, +irb+), you could applyoptions using the +RUBYOPT+ +# environment variable: +# +# $ RUBYOPT='--disable-did_you_mean' irb +# irb:0> 1.zeor? +# # => NoMethodError (undefined method `zeor?' for 1:Integer) +# +# +# == Getting the original error message +# +# Sometimes, you do not want to disable the gem entirely, but need to get the +# original error message without suggestions (e.g. testing). In this case, you +# could use the +#original_message+ method on the error object: +# +# no_method_error = begin +# 1.zeor? +# rescue NoMethodError => error +# error +# end +# +# no_method_error.message +# # => NoMethodError (undefined method `zeor?' for 1:Integer) +# # Did you mean? zero? +# +# no_method_error.original_message +# # => NoMethodError (undefined method `zeor?' for 1:Integer) +# +module DidYouMean + # Map of error types and spell checker objects. + SPELL_CHECKERS = Hash.new(NullChecker) + + # Adds +DidYouMean+ functionality to an error using a given spell checker + def self.correct_error(error_class, spell_checker) + SPELL_CHECKERS[error_class.name] = spell_checker + error_class.prepend(Correctable) unless error_class < Correctable + end + + correct_error NameError, NameErrorCheckers + correct_error KeyError, KeyErrorChecker + correct_error NoMethodError, MethodNameChecker + correct_error LoadError, RequirePathChecker if RUBY_VERSION >= '2.8.0' + + # Returns the currently set formatter. By default, it is set to +DidYouMean::Formatter+. + def self.formatter + @@formatter + end + + # Updates the primary formatter used to format the suggestions. + def self.formatter=(formatter) + @@formatter = formatter + end + + self.formatter = PlainFormatter.new +end diff --git a/Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/did_you_mean-1.5.0/lib/did_you_mean/core_ext/name_error.rb b/Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/did_you_mean-1.5.0/lib/did_you_mean/core_ext/name_error.rb new file mode 100644 index 0000000000..77dcd520c0 --- /dev/null +++ b/Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/did_you_mean-1.5.0/lib/did_you_mean/core_ext/name_error.rb @@ -0,0 +1,25 @@ +module DidYouMean + module Correctable + def original_message + method(:to_s).super_method.call + end + + def to_s + msg = super.dup + suggestion = DidYouMean.formatter.message_for(corrections) + + msg << suggestion if !msg.end_with?(suggestion) + msg + rescue + super + end + + def corrections + @corrections ||= spell_checker.corrections + end + + def spell_checker + SPELL_CHECKERS[self.class.to_s].new(self) + end + end +end diff --git a/Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/did_you_mean-1.5.0/lib/did_you_mean/experimental.rb b/Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/did_you_mean-1.5.0/lib/did_you_mean/experimental.rb new file mode 100644 index 0000000000..f8e37e4532 --- /dev/null +++ b/Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/did_you_mean-1.5.0/lib/did_you_mean/experimental.rb @@ -0,0 +1,2 @@ +warn "Experimental features in the did_you_mean gem has been removed " \ + "and `require \"did_you_mean/experimental\"' has no effect." diff --git a/Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/did_you_mean-1.5.0/lib/did_you_mean/formatters/plain_formatter.rb b/Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/did_you_mean-1.5.0/lib/did_you_mean/formatters/plain_formatter.rb new file mode 100644 index 0000000000..e2d995f587 --- /dev/null +++ b/Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/did_you_mean-1.5.0/lib/did_you_mean/formatters/plain_formatter.rb @@ -0,0 +1,33 @@ +# frozen-string-literal: true + +module DidYouMean + # The +DidYouMean::PlainFormatter+ is the basic, default formatter for the + # gem. The formatter responds to the +message_for+ method and it returns a + # human readable string. + class PlainFormatter + + # Returns a human readable string that contains +corrections+. This + # formatter is designed to be less verbose to not take too much screen + # space while being helpful enough to the user. + # + # @example + # + # formatter = DidYouMean::PlainFormatter.new + # + # # displays suggestions in two lines with the leading empty line + # puts formatter.message_for(["methods", "method"]) + # + # Did you mean? methods + # method + # # => nil + # + # # displays an empty line + # puts formatter.message_for([]) + # + # # => nil + # + def message_for(corrections) + corrections.empty? ? "" : "\nDid you mean? #{corrections.join("\n ")}" + end + end +end diff --git a/Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/did_you_mean-1.5.0/lib/did_you_mean/formatters/verbose_formatter.rb b/Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/did_you_mean-1.5.0/lib/did_you_mean/formatters/verbose_formatter.rb new file mode 100644 index 0000000000..b8fe214d57 --- /dev/null +++ b/Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/did_you_mean-1.5.0/lib/did_you_mean/formatters/verbose_formatter.rb @@ -0,0 +1,49 @@ +# frozen-string-literal: true + +module DidYouMean + # The +DidYouMean::VerboseFormatter+ uses extra empty lines to make the + # suggestion stand out more in the error message. + # + # In order to activate the verbose formatter, + # + # @example + # + # OBject + # # => NameError: uninitialized constant OBject + # # Did you mean? Object + # + # require 'did_you_mean/verbose' + # + # OBject + # # => NameError: uninitialized constant OBject + # # + # # Did you mean? Object + # # + # + class VerboseFormatter + + # Returns a human readable string that contains +corrections+. This + # formatter is designed to be less verbose to not take too much screen + # space while being helpful enough to the user. + # + # @example + # + # formatter = DidYouMean::PlainFormatter.new + # + # puts formatter.message_for(["methods", "method"]) + # + # + # Did you mean? methods + # method + # + # # => nil + # + def message_for(corrections) + return "" if corrections.empty? + + output = "\n\n Did you mean? ".dup + output << corrections.join("\n ") + output << "\n " + end + end +end diff --git a/Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/did_you_mean-1.5.0/lib/did_you_mean/jaro_winkler.rb b/Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/did_you_mean-1.5.0/lib/did_you_mean/jaro_winkler.rb new file mode 100644 index 0000000000..56db130af4 --- /dev/null +++ b/Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/did_you_mean-1.5.0/lib/did_you_mean/jaro_winkler.rb @@ -0,0 +1,87 @@ +module DidYouMean + module Jaro + module_function + + def distance(str1, str2) + str1, str2 = str2, str1 if str1.length > str2.length + length1, length2 = str1.length, str2.length + + m = 0.0 + t = 0.0 + range = (length2 / 2).floor - 1 + range = 0 if range < 0 + flags1 = 0 + flags2 = 0 + + # Avoid duplicating enumerable objects + str1_codepoints = str1.codepoints + str2_codepoints = str2.codepoints + + i = 0 + while i < length1 + last = i + range + j = (i >= range) ? i - range : 0 + + while j <= last + if flags2[j] == 0 && str1_codepoints[i] == str2_codepoints[j] + flags2 |= (1 << j) + flags1 |= (1 << i) + m += 1 + break + end + + j += 1 + end + + i += 1 + end + + k = i = 0 + while i < length1 + if flags1[i] != 0 + j = index = k + + k = while j < length2 + index = j + break(j + 1) if flags2[j] != 0 + + j += 1 + end + + t += 1 if str1_codepoints[i] != str2_codepoints[index] + end + + i += 1 + end + t = (t / 2).floor + + m == 0 ? 0 : (m / length1 + m / length2 + (m - t) / m) / 3 + end + end + + module JaroWinkler + WEIGHT = 0.1 + THRESHOLD = 0.7 + + module_function + + def distance(str1, str2) + jaro_distance = Jaro.distance(str1, str2) + + if jaro_distance > THRESHOLD + codepoints2 = str2.codepoints + prefix_bonus = 0 + + i = 0 + str1.each_codepoint do |char1| + char1 == codepoints2[i] && i < 4 ? prefix_bonus += 1 : break + i += 1 + end + + jaro_distance + (prefix_bonus * WEIGHT * (1 - jaro_distance)) + else + jaro_distance + end + end + end +end diff --git a/Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/did_you_mean-1.5.0/lib/did_you_mean/levenshtein.rb b/Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/did_you_mean-1.5.0/lib/did_you_mean/levenshtein.rb new file mode 100644 index 0000000000..098053470f --- /dev/null +++ b/Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/did_you_mean-1.5.0/lib/did_you_mean/levenshtein.rb @@ -0,0 +1,57 @@ +module DidYouMean + module Levenshtein # :nodoc: + # This code is based directly on the Text gem implementation + # Copyright (c) 2006-2013 Paul Battley, Michael Neumann, Tim Fletcher. + # + # Returns a value representing the "cost" of transforming str1 into str2 + def distance(str1, str2) + n = str1.length + m = str2.length + return m if n.zero? + return n if m.zero? + + d = (0..m).to_a + x = nil + + # to avoid duplicating an enumerable object, create it outside of the loop + str2_codepoints = str2.codepoints + + str1.each_codepoint.with_index(1) do |char1, i| + j = 0 + while j < m + cost = (char1 == str2_codepoints[j]) ? 0 : 1 + x = min3( + d[j+1] + 1, # insertion + i + 1, # deletion + d[j] + cost # substitution + ) + d[j] = i + i = x + + j += 1 + end + d[m] = x + end + + x + end + module_function :distance + + private + + # detects the minimum value out of three arguments. This method is + # faster than `[a, b, c].min` and puts less GC pressure. + # See https://github.com/ruby/did_you_mean/pull/1 for a performance + # benchmark. + def min3(a, b, c) + if a < b && a < c + a + elsif b < c + b + else + c + end + end + module_function :min3 + end +end diff --git a/Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/did_you_mean-1.5.0/lib/did_you_mean/spell_checker.rb b/Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/did_you_mean-1.5.0/lib/did_you_mean/spell_checker.rb new file mode 100644 index 0000000000..e5106abba2 --- /dev/null +++ b/Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/did_you_mean-1.5.0/lib/did_you_mean/spell_checker.rb @@ -0,0 +1,46 @@ +# frozen-string-literal: true + +require_relative "levenshtein" +require_relative "jaro_winkler" + +module DidYouMean + class SpellChecker + def initialize(dictionary:) + @dictionary = dictionary + end + + def correct(input) + input = normalize(input) + threshold = input.length > 3 ? 0.834 : 0.77 + + words = @dictionary.select { |word| JaroWinkler.distance(normalize(word), input) >= threshold } + words.reject! { |word| input == word.to_s } + words.sort_by! { |word| JaroWinkler.distance(word.to_s, input) } + words.reverse! + + # Correct mistypes + threshold = (input.length * 0.25).ceil + corrections = words.select { |c| Levenshtein.distance(normalize(c), input) <= threshold } + + # Correct misspells + if corrections.empty? + corrections = words.select do |word| + word = normalize(word) + length = input.length < word.length ? input.length : word.length + + Levenshtein.distance(word, input) < length + end.first(1) + end + + corrections + end + + private + + def normalize(str_or_symbol) #:nodoc: + str = str_or_symbol.to_s.downcase + str.tr!("@", "") + str + end + end +end diff --git a/Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/did_you_mean-1.5.0/lib/did_you_mean/spell_checkers/key_error_checker.rb b/Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/did_you_mean-1.5.0/lib/did_you_mean/spell_checkers/key_error_checker.rb new file mode 100644 index 0000000000..be4bea7789 --- /dev/null +++ b/Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/did_you_mean-1.5.0/lib/did_you_mean/spell_checkers/key_error_checker.rb @@ -0,0 +1,20 @@ +require_relative "../spell_checker" + +module DidYouMean + class KeyErrorChecker + def initialize(key_error) + @key = key_error.key + @keys = key_error.receiver.keys + end + + def corrections + @corrections ||= exact_matches.empty? ? SpellChecker.new(dictionary: @keys).correct(@key).map(&:inspect) : exact_matches + end + + private + + def exact_matches + @exact_matches ||= @keys.select { |word| @key == word.to_s }.map(&:inspect) + end + end +end diff --git a/Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/did_you_mean-1.5.0/lib/did_you_mean/spell_checkers/method_name_checker.rb b/Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/did_you_mean-1.5.0/lib/did_you_mean/spell_checkers/method_name_checker.rb new file mode 100644 index 0000000000..0483127d6f --- /dev/null +++ b/Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/did_you_mean-1.5.0/lib/did_you_mean/spell_checkers/method_name_checker.rb @@ -0,0 +1,69 @@ +require_relative "../spell_checker" + +module DidYouMean + class MethodNameChecker + attr_reader :method_name, :receiver + + NAMES_TO_EXCLUDE = { NilClass => nil.methods } + NAMES_TO_EXCLUDE.default = [] + + # +MethodNameChecker::RB_RESERVED_WORDS+ is the list of reserved words in + # Ruby that take an argument. Unlike + # +VariableNameChecker::RB_RESERVED_WORDS+, these reserved words require + # an argument, and a +NoMethodError+ is raised due to the presence of the + # argument. + # + # The +MethodNameChecker+ will use this list to suggest a reversed word if + # a +NoMethodError+ is raised and found closest matches. + # + # Also see +VariableNameChecker::RB_RESERVED_WORDS+. + RB_RESERVED_WORDS = %i( + alias + case + def + defined? + elsif + end + ensure + for + rescue + super + undef + unless + until + when + while + yield + ) + + def initialize(exception) + @method_name = exception.name + @receiver = exception.receiver + @private_call = exception.respond_to?(:private_call?) ? exception.private_call? : false + end + + def corrections + @corrections ||= begin + dictionary = method_names + dictionary = RB_RESERVED_WORDS + dictionary if @private_call + + SpellChecker.new(dictionary: dictionary).correct(method_name) - names_to_exclude + end + end + + def method_names + if Object === receiver + method_names = receiver.methods + receiver.singleton_methods + method_names += receiver.private_methods if @private_call + method_names.uniq! + method_names + else + [] + end + end + + def names_to_exclude + Object === receiver ? NAMES_TO_EXCLUDE[receiver.class] : [] + end + end +end diff --git a/Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/did_you_mean-1.5.0/lib/did_you_mean/spell_checkers/name_error_checkers.rb b/Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/did_you_mean-1.5.0/lib/did_you_mean/spell_checkers/name_error_checkers.rb new file mode 100644 index 0000000000..6e2aaa4cb1 --- /dev/null +++ b/Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/did_you_mean-1.5.0/lib/did_you_mean/spell_checkers/name_error_checkers.rb @@ -0,0 +1,20 @@ +require_relative 'name_error_checkers/class_name_checker' +require_relative 'name_error_checkers/variable_name_checker' + +module DidYouMean + class << (NameErrorCheckers = Object.new) + def new(exception) + case exception.original_message + when /uninitialized constant/ + ClassNameChecker + when /undefined local variable or method/, + /undefined method/, + /uninitialized class variable/, + /no member '.*' in struct/ + VariableNameChecker + else + NullChecker + end.new(exception) + end + end +end diff --git a/Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/did_you_mean-1.5.0/lib/did_you_mean/spell_checkers/name_error_checkers/class_name_checker.rb b/Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/did_you_mean-1.5.0/lib/did_you_mean/spell_checkers/name_error_checkers/class_name_checker.rb new file mode 100644 index 0000000000..924265b929 --- /dev/null +++ b/Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/did_you_mean-1.5.0/lib/did_you_mean/spell_checkers/name_error_checkers/class_name_checker.rb @@ -0,0 +1,49 @@ +# frozen-string-literal: true + +require_relative "../../spell_checker" + +module DidYouMean + class ClassNameChecker + attr_reader :class_name + + def initialize(exception) + @class_name, @receiver, @original_message = exception.name, exception.receiver, exception.original_message + end + + def corrections + @corrections ||= SpellChecker.new(dictionary: class_names) + .correct(class_name) + .map(&:full_name) + .reject {|qualified_name| @original_message.include?(qualified_name) } + end + + def class_names + scopes.flat_map do |scope| + scope.constants.map do |c| + ClassName.new(c, scope == Object ? "" : "#{scope}::") + end + end + end + + def scopes + @scopes ||= @receiver.to_s.split("::").inject([Object]) do |_scopes, scope| + _scopes << _scopes.last.const_get(scope) + end.uniq + end + + class ClassName < String + attr :namespace + + def initialize(name, namespace = '') + super(name.to_s) + @namespace = namespace + end + + def full_name + self.class.new("#{namespace}#{self}") + end + end + + private_constant :ClassName + end +end diff --git a/Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/did_you_mean-1.5.0/lib/did_you_mean/spell_checkers/name_error_checkers/variable_name_checker.rb b/Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/did_you_mean-1.5.0/lib/did_you_mean/spell_checkers/name_error_checkers/variable_name_checker.rb new file mode 100644 index 0000000000..3e51b4fa3a --- /dev/null +++ b/Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/did_you_mean-1.5.0/lib/did_you_mean/spell_checkers/name_error_checkers/variable_name_checker.rb @@ -0,0 +1,82 @@ +# frozen-string-literal: true + +require_relative "../../spell_checker" + +module DidYouMean + class VariableNameChecker + attr_reader :name, :method_names, :lvar_names, :ivar_names, :cvar_names + + NAMES_TO_EXCLUDE = { 'foo' => [:fork, :for] } + NAMES_TO_EXCLUDE.default = [] + + # +VariableNameChecker::RB_RESERVED_WORDS+ is the list of all reserved + # words in Ruby. They could be declared like methods are, and a typo would + # cause Ruby to raise a +NameError+ because of the way they are declared. + # + # The +:VariableNameChecker+ will use this list to suggest a reversed word + # if a +NameError+ is raised and found closest matches, excluding: + # + # * +do+ + # * +if+ + # * +in+ + # * +or+ + # + # Also see +MethodNameChecker::RB_RESERVED_WORDS+. + RB_RESERVED_WORDS = %i( + BEGIN + END + alias + and + begin + break + case + class + def + defined? + else + elsif + end + ensure + false + for + module + next + nil + not + redo + rescue + retry + return + self + super + then + true + undef + unless + until + when + while + yield + __LINE__ + __FILE__ + __ENCODING__ + ) + + def initialize(exception) + @name = exception.name.to_s.tr("@", "") + @lvar_names = exception.respond_to?(:local_variables) ? exception.local_variables : [] + receiver = exception.receiver + + @method_names = receiver.methods + receiver.private_methods + @ivar_names = receiver.instance_variables + @cvar_names = receiver.class.class_variables + @cvar_names += receiver.class_variables if receiver.kind_of?(Module) + end + + def corrections + @corrections ||= SpellChecker + .new(dictionary: (RB_RESERVED_WORDS + lvar_names + method_names + ivar_names + cvar_names)) + .correct(name) - NAMES_TO_EXCLUDE[@name] + end + end +end diff --git a/Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/did_you_mean-1.5.0/lib/did_you_mean/spell_checkers/null_checker.rb b/Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/did_you_mean-1.5.0/lib/did_you_mean/spell_checkers/null_checker.rb new file mode 100644 index 0000000000..1306f69d4a --- /dev/null +++ b/Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/did_you_mean-1.5.0/lib/did_you_mean/spell_checkers/null_checker.rb @@ -0,0 +1,6 @@ +module DidYouMean + class NullChecker + def initialize(*); end + def corrections; [] end + end +end diff --git a/Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/did_you_mean-1.5.0/lib/did_you_mean/spell_checkers/require_path_checker.rb b/Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/did_you_mean-1.5.0/lib/did_you_mean/spell_checkers/require_path_checker.rb new file mode 100644 index 0000000000..aaf877b412 --- /dev/null +++ b/Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/did_you_mean-1.5.0/lib/did_you_mean/spell_checkers/require_path_checker.rb @@ -0,0 +1,35 @@ +# frozen-string-literal: true + +require_relative "../spell_checker" +require_relative "../tree_spell_checker" + +module DidYouMean + class RequirePathChecker + attr_reader :path + + INITIAL_LOAD_PATH = $LOAD_PATH.dup.freeze + ENV_SPECIFIC_EXT = ".#{RbConfig::CONFIG["DLEXT"]}" + + private_constant :INITIAL_LOAD_PATH, :ENV_SPECIFIC_EXT + + def self.requireables + @requireables ||= INITIAL_LOAD_PATH + .flat_map {|path| Dir.glob("**/???*{.rb,#{ENV_SPECIFIC_EXT}}", base: path) } + .map {|path| path.chomp!(".rb") || path.chomp!(ENV_SPECIFIC_EXT) } + end + + def initialize(exception) + @path = exception.path + end + + def corrections + @corrections ||= begin + threshold = path.size * 2 + dictionary = self.class.requireables.reject {|str| str.size >= threshold } + spell_checker = path.include?("/") ? TreeSpellChecker : SpellChecker + + spell_checker.new(dictionary: dictionary).correct(path).uniq + end + end + end +end diff --git a/Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/did_you_mean-1.5.0/lib/did_you_mean/tree_spell_checker.rb b/Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/did_you_mean-1.5.0/lib/did_you_mean/tree_spell_checker.rb new file mode 100644 index 0000000000..799f07fcf0 --- /dev/null +++ b/Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/did_you_mean-1.5.0/lib/did_you_mean/tree_spell_checker.rb @@ -0,0 +1,109 @@ +# frozen_string_literal: true + +module DidYouMean + # spell checker for a dictionary that has a tree + # structure, see doc/tree_spell_checker_api.md + class TreeSpellChecker + attr_reader :dictionary, :separator, :augment + + def initialize(dictionary:, separator: '/', augment: nil) + @dictionary = dictionary + @separator = separator + @augment = augment + end + + def correct(input) + plausibles = plausible_dimensions(input) + return fall_back_to_normal_spell_check(input) if plausibles.empty? + + suggestions = find_suggestions(input, plausibles) + return fall_back_to_normal_spell_check(input) if suggestions.empty? + + suggestions + end + + def dictionary_without_leaves + @dictionary_without_leaves ||= dictionary.map { |word| word.split(separator)[0..-2] }.uniq + end + + def tree_depth + @tree_depth ||= dictionary_without_leaves.max { |a, b| a.size <=> b.size }.size + end + + def dimensions + @dimensions ||= tree_depth.times.map do |index| + dictionary_without_leaves.map { |element| element[index] }.compact.uniq + end + end + + def find_leaves(path) + path_with_separator = "#{path}#{separator}" + + dictionary + .select {|str| str.include?(path_with_separator) } + .map {|str| str.gsub(path_with_separator, '') } + end + + def plausible_dimensions(input) + input.split(separator)[0..-2] + .map + .with_index { |element, index| correct_element(dimensions[index], element) if dimensions[index] } + .compact + end + + def possible_paths(states) + states.map { |state| state.join(separator) } + end + + private + + def find_suggestions(input, plausibles) + states = plausibles[0].product(*plausibles[1..-1]) + paths = possible_paths(states) + leaf = input.split(separator).last + + find_ideas(paths, leaf) + end + + def fall_back_to_normal_spell_check(input) + return [] unless augment + + ::DidYouMean::SpellChecker.new(dictionary: dictionary).correct(input) + end + + def find_ideas(paths, leaf) + paths.flat_map do |path| + names = find_leaves(path) + ideas = correct_element(names, leaf) + + ideas_to_paths(ideas, leaf, names, path) + end.compact + end + + def ideas_to_paths(ideas, leaf, names, path) + if ideas.empty? + nil + elsif names.include?(leaf) + ["#{path}#{separator}#{leaf}"] + else + ideas.map {|str| "#{path}#{separator}#{str}" } + end + end + + def correct_element(names, element) + return names if names.size == 1 + + str = normalize(element) + + return [str] if names.include?(str) + + ::DidYouMean::SpellChecker.new(dictionary: names).correct(str) + end + + def normalize(str) + str.downcase! + str.tr!('@', ' ') if str.include?('@') + str + end + end +end diff --git a/Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/did_you_mean-1.5.0/lib/did_you_mean/verbose.rb b/Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/did_you_mean-1.5.0/lib/did_you_mean/verbose.rb new file mode 100644 index 0000000000..4e86f167ea --- /dev/null +++ b/Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/did_you_mean-1.5.0/lib/did_you_mean/verbose.rb @@ -0,0 +1,4 @@ +require_relative '../did_you_mean' +require_relative 'formatters/verbose_formatter' + +DidYouMean.formatter = DidYouMean::VerboseFormatter.new diff --git a/Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/did_you_mean-1.5.0/lib/did_you_mean/version.rb b/Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/did_you_mean-1.5.0/lib/did_you_mean/version.rb new file mode 100644 index 0000000000..9a53a3185a --- /dev/null +++ b/Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/did_you_mean-1.5.0/lib/did_you_mean/version.rb @@ -0,0 +1,3 @@ +module DidYouMean + VERSION = "1.5.0" +end From 35288995bcf2ac522cb146dde5d5f99c75cff9f7 Mon Sep 17 00:00:00 2001 From: Alexander Bayandin Date: Tue, 22 Jun 2021 16:36:03 +0100 Subject: [PATCH 6/6] cmd/install: do not print similar error messages --- Library/Homebrew/cmd/install.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/Library/Homebrew/cmd/install.rb b/Library/Homebrew/cmd/install.rb index deb35c677d..276700e6bc 100644 --- a/Library/Homebrew/cmd/install.rb +++ b/Library/Homebrew/cmd/install.rb @@ -268,7 +268,6 @@ module Homebrew puts "To install one of them, run (for example):\n brew install #{formulae_search_results.first}" end - ofail e.message if (reason = MissingFormula.reason(e.name)) $stderr.puts reason return