diff --git a/Library/Homebrew/cask/lib/hbc/cli/abstract_command.rb b/Library/Homebrew/cask/lib/hbc/cli/abstract_command.rb index 001a9623b2..2cfe3c467d 100644 --- a/Library/Homebrew/cask/lib/hbc/cli/abstract_command.rb +++ b/Library/Homebrew/cask/lib/hbc/cli/abstract_command.rb @@ -9,7 +9,7 @@ module Hbc option "--debug", :debug, false option "--verbose", :verbose, false option "--outdated", :outdated_only, false - option "--require-sha", :require_sha, false + option "--require-sha", :require_sha, false def self.command_name @command_name ||= name.sub(/^.*:/, "").gsub(/(.)([A-Z])/, '\1_\2').downcase @@ -49,24 +49,18 @@ module Hbc casks = args.empty? ? alternative.call : args @casks = casks.map { |cask| CaskLoader.load(cask) } rescue CaskUnavailableError => e - reason = [e.reason, suggestion_message(e.token)].join(" ") + reason = [e.reason, *suggestion_message(e.token)].join(" ") raise e.class.new(e.token, reason) end def suggestion_message(cask_token) - exact_match, partial_matches = Search.search(cask_token) + matches, = Search.search(cask_token) - if exact_match.nil? && partial_matches.count == 1 - exact_match = partial_matches.first - end - - if exact_match - "Did you mean “#{exact_match}”?" - elsif !partial_matches.empty? + if matches.one? + "Did you mean “#{matches.first}”?" + elsif !matches.empty? "Did you mean one of these?\n" - .concat(Formatter.columns(partial_matches.take(20))) - else - "" + .concat(Formatter.columns(matches.take(20))) end end end diff --git a/Library/Homebrew/cask/lib/hbc/cli/search.rb b/Library/Homebrew/cask/lib/hbc/cli/search.rb index 3decd6bd8d..bda5374d53 100644 --- a/Library/Homebrew/cask/lib/hbc/cli/search.rb +++ b/Library/Homebrew/cask/lib/hbc/cli/search.rb @@ -1,6 +1,10 @@ +require "search" + module Hbc class CLI class Search < AbstractCommand + extend Homebrew::Search + def run if args.empty? puts Formatter.columns(CLI.nice_listing(Cask.map(&:qualified_token))) @@ -18,28 +22,7 @@ module Hbc end end - def self.search_remote(query) - matches = begin - GitHub.search_code( - user: "Homebrew", - path: "Casks", - filename: query, - extension: "rb", - ) - rescue GitHub::Error => error - opoo "Error searching on GitHub: #{error}\n" - [] - end - - matches.map do |match| - tap = Tap.fetch(match["repository"]["full_name"]) - next if tap.installed? - "#{tap.name}/#{File.basename(match["path"], ".rb")}" - end.compact - end - def self.search(*arguments) - exact_match = nil partial_matches = [] search_term = arguments.join(" ") search_regexp = extract_regexp arguments.first @@ -50,36 +33,30 @@ module Hbc else simplified_tokens = all_tokens.map { |t| t.sub(%r{^.*\/}, "").gsub(/[^a-z0-9]+/i, "") } simplified_search_term = search_term.sub(/\.rb$/i, "").gsub(/[^a-z0-9]+/i, "") - exact_match = simplified_tokens.grep(/^#{simplified_search_term}$/i) { |t| all_tokens[simplified_tokens.index(t)] }.first partial_matches = simplified_tokens.grep(/#{simplified_search_term}/i) { |t| all_tokens[simplified_tokens.index(t)] } - partial_matches.delete(exact_match) end - remote_matches = search_remote(search_term) + remote_matches = search_taps(search_term, silent: true)[:casks] - [exact_match, partial_matches, remote_matches, search_term] + [partial_matches, remote_matches, search_term] end - def self.render_results(exact_match, partial_matches, remote_matches, search_term) + def self.render_results(partial_matches, remote_matches, search_term) unless $stdout.tty? - puts [*exact_match, *partial_matches, *remote_matches] + puts [*partial_matches, *remote_matches] return end - if !exact_match && partial_matches.empty? && remote_matches.empty? + if partial_matches.empty? && remote_matches.empty? puts "No Cask found for \"#{search_term}\"." return end - if exact_match - ohai "Exact Match" - puts highlight_installed exact_match - end unless partial_matches.empty? if extract_regexp search_term ohai "Regexp Matches" else - ohai "Partial Matches" + ohai "Matches" end puts Formatter.columns(partial_matches.map(&method(:highlight_installed))) end diff --git a/Library/Homebrew/cli_parser.rb b/Library/Homebrew/cli_parser.rb index 0ea8af6a66..be8962363f 100644 --- a/Library/Homebrew/cli_parser.rb +++ b/Library/Homebrew/cli_parser.rb @@ -5,8 +5,8 @@ require "set" module Homebrew module CLI class Parser - def self.parse(&block) - new(&block).parse + def self.parse(args = ARGV, &block) + new(&block).parse(args) end def initialize(&block) @@ -60,17 +60,28 @@ module Homebrew @conflicts << options.map { |option| option_to_name(option) } end - def option_to_name(name) - name.sub(/\A--?/, "").tr("-", "_").delete("=") + def option_to_name(option) + option.sub(/\A--?/, "") + .tr("-", "_") + .delete("=") + end + + def name_to_option(name) + if name.length == 1 + "-#{name}" + else + "--#{name}" + end end def option_to_description(*names) names.map { |name| name.to_s.sub(/\A--?/, "").tr("-", " ") }.max end - def parse(cmdline_args = ARGV) - @parser.parse(cmdline_args) + def parse(cmdline_args) + remaining_args = @parser.parse(cmdline_args) check_constraint_violations + Homebrew.args[:remaining] = remaining_args end private @@ -126,7 +137,9 @@ module Homebrew violations = mutually_exclusive_options_group.select do |option| option_passed? option end - raise OptionConflictError, violations if violations.length > 1 + + next if violations.count < 2 + raise OptionConflictError, violations.map(&method(:name_to_option)) end end @@ -163,9 +176,10 @@ module Homebrew class OptionConflictError < RuntimeError def initialize(args) - args_list = args.join("` and `") + args_list = args.map(&Formatter.public_method(:option)) + .join(" and ") super <<~EOS - `#{args_list}` are mutually exclusive + Options #{args_list} are mutually exclusive. EOS end end diff --git a/Library/Homebrew/cmd/desc.rb b/Library/Homebrew/cmd/desc.rb index ccb7e99255..1a75371a02 100644 --- a/Library/Homebrew/cmd/desc.rb +++ b/Library/Homebrew/cmd/desc.rb @@ -9,11 +9,13 @@ #: first search, making that search slower than subsequent ones. require "descriptions" -require "cmd/search" +require "search" module Homebrew module_function + extend Search + def desc search_type = [] search_type << :either if ARGV.flag? "--search" @@ -28,9 +30,10 @@ module Homebrew results.print elsif search_type.size > 1 odie "Pick one, and only one, of -s/--search, -n/--name, or -d/--description." - elsif arg = ARGV.named.first - regex = Homebrew.query_regexp(arg) - results = Descriptions.search(regex, search_type.first) + elsif !ARGV.named.empty? + arg = ARGV.named.join(" ") + string_or_regex = query_regexp(arg) + results = Descriptions.search(string_or_regex, search_type.first) results.print else odie "You must provide a search term." diff --git a/Library/Homebrew/cmd/install.rb b/Library/Homebrew/cmd/install.rb index 4b699474e1..fc1f56fc0a 100644 --- a/Library/Homebrew/cmd/install.rb +++ b/Library/Homebrew/cmd/install.rb @@ -68,14 +68,16 @@ #: creating patches to the software. require "missing_formula" -require "cmd/search" require "formula_installer" require "development_tools" require "install" +require "search" module Homebrew module_function + extend Search + def install raise FormulaUnspecifiedError if ARGV.named.empty? @@ -261,10 +263,8 @@ module Homebrew return end - regex = query_regexp(e.name) - ohai "Searching for similarly named formulae..." - formulae_search_results = search_formulae(regex) + formulae_search_results = search_formulae(e.name) case formulae_search_results.length when 0 ofail "No similarly named formulae found." @@ -281,7 +281,7 @@ module Homebrew # Do not search taps if the formula name is qualified return if e.name.include?("/") ohai "Searching taps..." - taps_search_results = search_taps(e.name) + taps_search_results = search_taps(e.name)[:formulae] case taps_search_results.length when 0 ofail "No formulae found in taps." diff --git a/Library/Homebrew/cmd/search.rb b/Library/Homebrew/cmd/search.rb index a9afe5ad7d..dd45fe63ad 100644 --- a/Library/Homebrew/cmd/search.rb +++ b/Library/Homebrew/cmd/search.rb @@ -16,49 +16,69 @@ require "formula" require "missing_formula" require "descriptions" +require "cli_parser" +require "search" module Homebrew module_function - def search - if ARGV.empty? - puts Formatter.columns(Formula.full_names.sort) - elsif ARGV.include? "--macports" - exec_browser "https://www.macports.org/ports.php?by=name&substr=#{ARGV.next}" - elsif ARGV.include? "--fink" - exec_browser "http://pdb.finkproject.org/pdb/browse.php?summary=#{ARGV.next}" - elsif ARGV.include? "--debian" - exec_browser "https://packages.debian.org/search?keywords=#{ARGV.next}&searchon=names&suite=all§ion=all" - elsif ARGV.include? "--opensuse" - exec_browser "https://software.opensuse.org/search?q=#{ARGV.next}" - elsif ARGV.include? "--fedora" - exec_browser "https://apps.fedoraproject.org/packages/s/#{ARGV.next}" - elsif ARGV.include? "--ubuntu" - exec_browser "https://packages.ubuntu.com/search?keywords=#{ARGV.next}&searchon=names&suite=all§ion=all" - elsif ARGV.include? "--desc" - query = ARGV.next - regex = query_regexp(query) - Descriptions.search(regex, :desc).print - elsif ARGV.first =~ HOMEBREW_TAP_FORMULA_REGEX - query = ARGV.first + extend Search - begin - result = Formulary.factory(query).name - results = Array(result) - rescue FormulaUnavailableError - _, _, name = query.split("/", 3) - results = search_taps(name) + PACKAGE_MANAGERS = { + macports: ->(query) { "https://www.macports.org/ports.php?by=name&substr=#{query}" }, + fink: ->(query) { "http://pdb.finkproject.org/pdb/browse.php?summary=#{query}" }, + debian: ->(query) { "https://packages.debian.org/search?keywords=#{query}&searchon=names&suite=all§ion=all" }, + opensuse: ->(query) { "https://software.opensuse.org/search?q=#{query}" }, + fedora: ->(query) { "https://apps.fedoraproject.org/packages/s/#{query}" }, + ubuntu: ->(query) { "https://packages.ubuntu.com/search?keywords=#{query}&searchon=names&suite=all§ion=all" }, + }.freeze + + def search(argv = ARGV) + CLI::Parser.parse(argv) do + switch "--desc" + + package_manager_switches = PACKAGE_MANAGERS.keys.map { |name| "--#{name}" } + + package_manager_switches.each do |s| + switch s end - puts Formatter.columns(results.sort) unless results.empty? + conflicts(*package_manager_switches) + end + + if package_manager = PACKAGE_MANAGERS.detect { |name,| args[:"#{name}?"] } + _, url = package_manager + exec_browser url.call(URI.encode_www_form_component(args.remaining.join(" "))) + return + end + + if args.remaining.empty? + puts Formatter.columns(Formula.full_names.sort) + elsif args.desc? + query = args.remaining.join(" ") + string_or_regex = query_regexp(query) + Descriptions.search(string_or_regex, :desc).print + elsif args.remaining.first =~ HOMEBREW_TAP_FORMULA_REGEX + query = args.remaining.first + + results = begin + [Formulary.factory(query).name] + rescue FormulaUnavailableError + _, _, name = query.split("/", 3) + remote_results = search_taps(name) + [*remote_results[:formulae], *remote_results[:casks]].sort + end + + puts Formatter.columns(results) unless results.empty? else - query = ARGV.first - regex = query_regexp(query) - local_results = search_formulae(regex) + query = args.remaining.join(" ") + string_or_regex = query_regexp(query) + local_results = search_formulae(string_or_regex) puts Formatter.columns(local_results.sort) unless local_results.empty? - tap_results = search_taps(query) - puts Formatter.columns(tap_results.sort) unless tap_results.empty? + remote_results = search_taps(query) + tap_results = [*remote_results[:formulae], *remote_results[:casks]].sort + puts Formatter.columns(tap_results) unless tap_results.empty? if $stdout.tty? count = local_results.length + tap_results.length @@ -78,10 +98,10 @@ module Homebrew end return unless $stdout.tty? - return if ARGV.empty? + return if args.remaining.empty? metacharacters = %w[\\ | ( ) [ ] { } ^ $ * + ?].freeze return unless metacharacters.any? do |char| - ARGV.any? do |arg| + args.remaining.any? do |arg| arg.include?(char) && !arg.start_with?("/") end end @@ -90,67 +110,4 @@ module Homebrew Surround your query with /slashes/ to search locally by regex. EOS end - - def query_regexp(query) - case query - when %r{^/(.*)/$} then Regexp.new(Regexp.last_match(1)) - else /.*#{Regexp.escape(query)}.*/i - end - rescue RegexpError - odie "#{query} is not a valid regex" - end - - def search_taps(query, silent: false) - return [] if ENV["HOMEBREW_NO_GITHUB_API"] - - # Use stderr to avoid breaking parsed output - unless silent - $stderr.puts Formatter.headline("Searching taps on GitHub...", color: :blue) - end - - matches = begin - GitHub.search_code( - user: "Homebrew", - path: ["Formula", "HomebrewFormula", "Casks", "."], - filename: query, - extension: "rb", - ) - rescue GitHub::Error => error - opoo "Error searching on GitHub: #{error}\n" - [] - end - matches.map do |match| - filename = File.basename(match["path"], ".rb") - tap = Tap.fetch(match["repository"]["full_name"]) - next if tap.installed? && !tap.name.start_with?("homebrew/cask") - "#{tap.name}/#{filename}" - end.compact - end - - def search_formulae(regex) - # Use stderr to avoid breaking parsed output - $stderr.puts Formatter.headline("Searching local taps...", color: :blue) - - aliases = Formula.alias_full_names - results = (Formula.full_names + aliases).grep(regex).sort - - results.map do |name| - begin - formula = Formulary.factory(name) - canonical_name = formula.name - canonical_full_name = formula.full_name - rescue - canonical_name = canonical_full_name = name - end - - # Ignore aliases from results when the full name was also found - next if aliases.include?(name) && results.include?(canonical_full_name) - - if (HOMEBREW_CELLAR/canonical_name).directory? - pretty_installed(name) - else - name - end - end.compact - end end diff --git a/Library/Homebrew/descriptions.rb b/Library/Homebrew/descriptions.rb index 4db47e77f1..b14833e3d0 100644 --- a/Library/Homebrew/descriptions.rb +++ b/Library/Homebrew/descriptions.rb @@ -1,7 +1,11 @@ require "formula" require "formula_versions" +require "search" +require "searchable" class Descriptions + extend Homebrew::Search + CACHE_FILE = HOMEBREW_CACHE + "desc_cache.json" def self.cache @@ -94,16 +98,18 @@ class Descriptions end # Given a regex, find all formulae whose specified fields contain a match. - def self.search(regex, field = :either) + def self.search(string_or_regex, field = :either) ensure_cache + @cache.extend(Searchable) + results = case field when :name - @cache.select { |name, _| name =~ regex } + @cache.search(string_or_regex) { |name, _| name } when :desc - @cache.select { |_, desc| desc =~ regex } + @cache.search(string_or_regex) { |_, desc| desc } when :either - @cache.select { |name, desc| (name =~ regex) || (desc =~ regex) } + @cache.search(string_or_regex) end new(results) diff --git a/Library/Homebrew/dev-cmd/tests.rb b/Library/Homebrew/dev-cmd/tests.rb index 8eabbf4558..183f67d806 100644 --- a/Library/Homebrew/dev-cmd/tests.rb +++ b/Library/Homebrew/dev-cmd/tests.rb @@ -39,16 +39,12 @@ module Homebrew ENV.delete("HOMEBREW_CASK_OPTS") ENV.delete("HOMEBREW_TEMP") ENV.delete("HOMEBREW_LINKAGE_CACHE") + ENV.delete("HOMEBREW_NO_GITHUB_API") ENV["HOMEBREW_NO_ANALYTICS_THIS_RUN"] = "1" ENV["HOMEBREW_DEVELOPER"] = "1" ENV["HOMEBREW_NO_COMPAT"] = "1" if args.no_compat? ENV["HOMEBREW_TEST_GENERIC_OS"] = "1" if args.generic? - - if args.online? - ENV["HOMEBREW_TEST_ONLINE"] = "1" - else - ENV["HOMEBREW_NO_GITHUB_API"] = "1" - end + ENV["HOMEBREW_TEST_ONLINE"] = "1" if args.online? if args.coverage? ENV["HOMEBREW_TESTS_COVERAGE"] = "1" diff --git a/Library/Homebrew/search.rb b/Library/Homebrew/search.rb new file mode 100644 index 0000000000..bbabc2120d --- /dev/null +++ b/Library/Homebrew/search.rb @@ -0,0 +1,84 @@ +require "searchable" + +module Homebrew + module Search + def 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 search_taps(query, silent: false) + results = { formulae: [], casks: [] } + + return results if ENV["HOMEBREW_NO_GITHUB_API"] + + unless silent + # Use stderr to avoid breaking parsed output + $stderr.puts Formatter.headline("Searching taps on GitHub...", color: :blue) + end + + matches = begin + GitHub.search_code( + user: "Homebrew", + path: ["Formula", "Casks", "."], + filename: query, + extension: "rb", + ) + rescue GitHub::Error => error + opoo "Error searching on GitHub: #{error}\n" + return results + end + + matches.each do |match| + name = File.basename(match["path"], ".rb") + tap = Tap.fetch(match["repository"]["full_name"]) + full_name = "#{tap.name}/#{name}" + + next if tap.installed? && !match["path"].start_with?("Casks/") + + if match["path"].start_with?("Casks/") + results[:casks] = [*results[:casks], full_name].sort + else + results[:formulae] = [*results[:formulae], full_name].sort + end + end + + results + end + + def search_formulae(string_or_regex) + # Use stderr to avoid breaking parsed output + $stderr.puts Formatter.headline("Searching local taps...", color: :blue) + + aliases = Formula.alias_full_names + results = (Formula.full_names + aliases) + .extend(Searchable) + .search(string_or_regex) + .sort + + results.map do |name| + begin + formula = Formulary.factory(name) + canonical_name = formula.name + canonical_full_name = formula.full_name + rescue + canonical_name = canonical_full_name = name + end + + # Ignore aliases from results when the full name was also found + next if aliases.include?(name) && results.include?(canonical_full_name) + + if (HOMEBREW_CELLAR/canonical_name).directory? + pretty_installed(name) + else + name + end + end.compact + end + end +end diff --git a/Library/Homebrew/searchable.rb b/Library/Homebrew/searchable.rb new file mode 100644 index 0000000000..9fca0bfdaa --- /dev/null +++ b/Library/Homebrew/searchable.rb @@ -0,0 +1,31 @@ +module Searchable + def search(string_or_regex, &block) + case string_or_regex + when Regexp + search_regex(string_or_regex, &block) + else + search_string(string_or_regex.to_str, &block) + end + end + + private + + def simplify_string(string) + string.downcase.gsub(/[^a-z\d]/i, "") + end + + def search_regex(regex) + select do |*args| + args = yield(*args) if block_given? + [*args].any? { |arg| arg.match?(regex) } + end + end + + def search_string(string) + simplified_string = simplify_string(string) + select do |*args| + args = yield(*args) if block_given? + [*args].any? { |arg| simplify_string(arg).include?(simplified_string) } + end + end +end diff --git a/Library/Homebrew/test/cask/cli/search_spec.rb b/Library/Homebrew/test/cask/cli/search_spec.rb index 5d8ad30e4f..379a27104d 100644 --- a/Library/Homebrew/test/cask/cli/search_spec.rb +++ b/Library/Homebrew/test/cask/cli/search_spec.rb @@ -13,7 +13,7 @@ describe Hbc::CLI::Search, :cask do expect { Hbc::CLI::Search.run("local") }.to output(<<~EOS).to_stdout.as_tty - ==> Partial Matches + ==> Matches local-caffeine local-transmission EOS @@ -51,6 +51,8 @@ describe Hbc::CLI::Search, :cask do end it "doesn't output anything to non-TTY stdout when there are no matches" do + allow(GitHub).to receive(:search_code).and_return([]) + expect { Hbc::CLI::Search.run("foo-bar-baz") } .to not_to_output.to_stdout .and not_to_output.to_stderr @@ -94,9 +96,8 @@ describe Hbc::CLI::Search, :cask do expect { Hbc::CLI::Search.run("test-opera") }.to output(<<~EOS).to_stdout.as_tty - ==> Exact Match + ==> Matches test-opera - ==> Partial Matches test-opera-mail EOS end diff --git a/Library/Homebrew/test/cmd/search_remote_tap_spec.rb b/Library/Homebrew/test/cmd/search_remote_tap_spec.rb deleted file mode 100644 index 49678f3ace..0000000000 --- a/Library/Homebrew/test/cmd/search_remote_tap_spec.rb +++ /dev/null @@ -1,24 +0,0 @@ -require "cmd/search" - -describe Homebrew do - specify "#search_taps" do - # Otherwise the tested method returns [], regardless of our stub - ENV.delete("HOMEBREW_NO_GITHUB_API") - - json_response = { - "items" => [ - { - "path" => "Formula/some-formula.rb", - "repository" => { - "full_name" => "Homebrew/homebrew-foo", - }, - }, - ], - } - - allow(GitHub).to receive(:open_api).and_yield(json_response) - - expect(described_class.search_taps("some-formula")) - .to match(["homebrew/foo/some-formula"]) - end -end diff --git a/Library/Homebrew/test/cmd/search_spec.rb b/Library/Homebrew/test/cmd/search_spec.rb index 1e1ef8e180..556c863ca2 100644 --- a/Library/Homebrew/test/cmd/search_spec.rb +++ b/Library/Homebrew/test/cmd/search_spec.rb @@ -1,3 +1,5 @@ +require "cmd/search" + describe "brew search", :integration_test do before do setup_test_formula "testball" diff --git a/Library/Homebrew/test/search_spec.rb b/Library/Homebrew/test/search_spec.rb new file mode 100644 index 0000000000..1afcb1314a --- /dev/null +++ b/Library/Homebrew/test/search_spec.rb @@ -0,0 +1,65 @@ +require "search" + +describe Homebrew::Search do + subject(:mod) { Object.new } + + before do + mod.extend(described_class) + end + + describe "#search_taps" do + before do + ENV.delete("HOMEBREW_NO_GITHUB_API") + end + + it "does not raise if `HOMEBREW_NO_GITHUB_API` is set" do + ENV["HOMEBREW_NO_GITHUB_API"] = "1" + expect(mod.search_taps("some-formula")).to match(formulae: [], casks: []) + end + + it "does not raise if the network fails" do + allow(GitHub).to receive(:open_api).and_raise(GitHub::Error) + + expect(mod.search_taps("some-formula")) + .to match(formulae: [], casks: []) + end + + it "returns Formulae and Casks separately" do + json_response = { + "items" => [ + { + "path" => "Formula/some-formula.rb", + "repository" => { + "full_name" => "Homebrew/homebrew-foo", + }, + }, + { + "path" => "Casks/some-cask.rb", + "repository" => { + "full_name" => "Homebrew/homebrew-bar", + }, + }, + ], + } + + allow(GitHub).to receive(:open_api).and_yield(json_response) + + expect(mod.search_taps("some-formula")) + .to match(formulae: ["homebrew/foo/some-formula"], casks: ["homebrew/bar/some-cask"]) + end + end + + describe "#query_regexp" do + it "correctly parses a regex query" do + expect(mod.query_regexp("/^query$/")).to eq(/^query$/) + end + + it "returns the original string if it is not a regex query" do + expect(mod.query_regexp("query")).to eq("query") + end + + it "raises an error if the query is an invalid regex" do + expect { mod.query_regexp("/+/") }.to raise_error(/not a valid regex/) + end + end +end diff --git a/Library/Homebrew/test/searchable_spec.rb b/Library/Homebrew/test/searchable_spec.rb new file mode 100644 index 0000000000..aaf2247795 --- /dev/null +++ b/Library/Homebrew/test/searchable_spec.rb @@ -0,0 +1,30 @@ +require "searchable" + +describe Searchable do + subject { ary.extend(described_class) } + + let(:ary) { ["with-dashes"] } + + describe "#search" do + context "when given a block" do + let(:ary) { [["with-dashes", "withdashes"]] } + + it "searches by the selected argument" do + expect(subject.search(/withdashes/) { |_, short_name| short_name }).not_to be_empty + expect(subject.search(/withdashes/) { |long_name, _| long_name }).to be_empty + end + end + + context "when given a regex" do + it "does not simplify strings" do + expect(subject.search(/with\-dashes/)).to eq ["with-dashes"] + end + end + + context "when given a string" do + it "simplifies both the query and searched strings" do + expect(subject.search("with dashes")).to eq ["with-dashes"] + end + end + end +end diff --git a/Library/Homebrew/utils/formatter.rb b/Library/Homebrew/utils/formatter.rb index a29b43c8de..ec144bf2f8 100644 --- a/Library/Homebrew/utils/formatter.rb +++ b/Library/Homebrew/utils/formatter.rb @@ -15,6 +15,10 @@ module Formatter "#{Tty.green}#{string}#{Tty.default}" end + def option(string) + "#{Tty.bold}#{string}#{Tty.reset}" + end + def success(string, label: nil) label(label, string, :green) end