brew/Library/Homebrew/search.rb
Issy Long 45978435e7
rubocop: Use Sorbet/StrictSigil as it's better than comments
- Previously I thought that comments were fine to discourage people from
  wasting their time trying to bump things that used `undef` that Sorbet
  didn't support. But RuboCop is better at this since it'll complain if
  the comments are unnecessary.

- Suggested in https://github.com/Homebrew/brew/pull/18018#issuecomment-2283369501.

- I've gone for a mixture of `rubocop:disable` for the files that can't
  be `typed: strict` (use of undef, required before everything else, etc)
  and `rubocop:todo` for everything else that should be tried to make
  strictly typed. There's no functional difference between the two as
  `rubocop:todo` is `rubocop:disable` with a different name.

- And I entirely disabled the cop for the docs/ directory since
  `typed: strict` isn't going to gain us anything for some Markdown
  linting config files.

- This means that now it's easier to track what needs to be done rather
  than relying on checklists of files in our big Sorbet issue:

```shell
$ git grep 'typed: true # rubocop:todo Sorbet/StrictSigil' | wc -l
    268
```

- And this is confirmed working for new files:

```shell
$ git status
On branch use-rubocop-for-sorbet-strict-sigils
Untracked files:
  (use "git add <file>..." to include in what will be committed)
        Library/Homebrew/bad.rb
        Library/Homebrew/good.rb

nothing added to commit but untracked files present (use "git add" to track)

$ brew style
Offenses:

bad.rb:1:1: C: Sorbet/StrictSigil: Sorbet sigil should be at least strict got true.
^^^^^^^^^^^^^

1340 files inspected, 1 offense detected
```
2024-08-12 15:24:27 +01:00

174 lines
5.6 KiB
Ruby

# typed: true # rubocop:todo Sorbet/StrictSigil
# frozen_string_literal: true
require "description_cache_store"
module Homebrew
# Helper module for searching formulae or casks.
module Search
def self.query_regexp(query)
if (m = query.match(%r{^/(.*)/$}))
Regexp.new(m[1])
else
query
end
rescue RegexpError
raise "#{query} is not a valid regex."
end
def self.search_descriptions(string_or_regex, args, search_type: :desc)
both = !args.formula? && !args.cask?
eval_all = args.eval_all? || Homebrew::EnvConfig.eval_all?
if args.formula? || both
ohai "Formulae"
if eval_all
CacheStoreDatabase.use(:descriptions) do |db|
cache_store = DescriptionCacheStore.new(db)
Descriptions.search(string_or_regex, search_type, cache_store, eval_all).print
end
else
unofficial = Tap.all.sum { |tap| tap.official? ? 0 : tap.formula_files.size }
if unofficial.positive?
opoo "Use `--eval-all` to search #{unofficial} additional " \
"#{Utils.pluralize("formula", unofficial, plural: "e")} in third party taps."
end
descriptions = Homebrew::API::Formula.all_formulae.transform_values { |data| data["desc"] }
Descriptions.search(string_or_regex, search_type, descriptions, eval_all, cache_store_hash: true).print
end
end
return if !args.cask? && !both
puts if both
ohai "Casks"
if eval_all
CacheStoreDatabase.use(:cask_descriptions) do |db|
cache_store = CaskDescriptionCacheStore.new(db)
Descriptions.search(string_or_regex, search_type, cache_store, eval_all).print
end
else
unofficial = Tap.all.sum { |tap| tap.official? ? 0 : tap.cask_files.size }
if unofficial.positive?
opoo "Use `--eval-all` to search #{unofficial} additional " \
"#{Utils.pluralize("cask", unofficial)} in third party taps."
end
descriptions = Homebrew::API::Cask.all_casks.transform_values { |c| [c["name"].join(", "), c["desc"]] }
Descriptions.search(string_or_regex, search_type, descriptions, eval_all, cache_store_hash: true).print
end
end
def self.search_formulae(string_or_regex)
if string_or_regex.is_a?(String) && string_or_regex.match?(HOMEBREW_TAP_FORMULA_REGEX)
return begin
[Formulary.factory(string_or_regex).name]
rescue FormulaUnavailableError
[]
end
end
aliases = Formula.alias_full_names
results = search(Formula.full_names + aliases, string_or_regex).sort
results |= Formula.fuzzy_search(string_or_regex).map { |n| Formulary.factory(n).full_name }
results.filter_map do |name|
formula, canonical_full_name = begin
f = Formulary.factory(name)
[f, f.full_name]
rescue
[nil, name]
end
# Ignore aliases from results when the full name was also found
next if aliases.include?(name) && results.include?(canonical_full_name)
if formula&.any_version_installed?
pretty_installed(name)
elsif formula.nil? || formula.valid_platform?
name
end
end
end
def self.search_casks(string_or_regex)
if string_or_regex.is_a?(String) && string_or_regex.match?(HOMEBREW_TAP_CASK_REGEX)
return begin
[Cask::CaskLoader.load(string_or_regex).token]
rescue Cask::CaskUnavailableError
[]
end
end
cask_tokens = Tap.each_with_object([]) do |tap, array|
# We can exclude the core cask tap because `CoreCaskTap#cask_tokens` returns short names by default.
if tap.official? && !tap.core_cask_tap?
tap.cask_tokens.each { |token| array << token.sub(%r{^homebrew/cask.*/}, "") }
else
tap.cask_tokens.each { |token| array << token }
end
end.uniq
results = search(cask_tokens, string_or_regex)
results += DidYouMean::SpellChecker.new(dictionary: cask_tokens)
.correct(string_or_regex)
results.sort.map do |name|
cask = Cask::CaskLoader.load(name)
if cask.installed?
pretty_installed(cask.full_name)
else
cask.full_name
end
end.uniq
end
def self.search_names(string_or_regex, args)
both = !args.formula? && !args.cask?
all_formulae = if args.formula? || both
search_formulae(string_or_regex)
else
[]
end
all_casks = if args.cask? || both
search_casks(string_or_regex)
else
[]
end
[all_formulae, all_casks]
end
def self.search(selectable, string_or_regex, &block)
case string_or_regex
when Regexp
search_regex(selectable, string_or_regex, &block)
else
search_string(selectable, string_or_regex.to_str, &block)
end
end
def self.simplify_string(string)
string.downcase.gsub(/[^a-z\d]/i, "")
end
def self.search_regex(selectable, regex)
selectable.select do |*args|
args = yield(*args) if block_given?
args = Array(args).flatten.compact
args.any? { |arg| arg.match?(regex) }
end
end
def self.search_string(selectable, string)
simplified_string = simplify_string(string)
selectable.select do |*args|
args = yield(*args) if block_given?
args = Array(args).flatten.compact
args.any? { |arg| simplify_string(arg).include?(simplified_string) }
end
end
end
end