require "formula" require "formula_versions" require "search" require "searchable" class Descriptions extend Homebrew::Search CACHE_FILE = HOMEBREW_CACHE + "desc_cache.json" def self.cache @cache || load_cache end # If the cache file exists, load it into, and return, a hash; otherwise, # return nil. def self.load_cache @cache = JSON.parse(CACHE_FILE.read) if CACHE_FILE.exist? end # Write the cache to disk after ensuring the existence of the containing # directory. def self.save_cache HOMEBREW_CACHE.mkpath CACHE_FILE.atomic_write JSON.dump(@cache) end # Create a hash mapping all formulae to their descriptions; # save it for future use. def self.generate_cache @cache = {} Formula.each do |f| @cache[f.full_name] = f.desc end save_cache end # Return true if the cache exists, and none of the Taps # repos were updated more recently than it was. def self.cache_fresh? return false unless CACHE_FILE.exist? cache_mtime = File.mtime(CACHE_FILE) Tap.each do |tap| next unless tap.git? repo_mtime = File.mtime(tap.path/".git/refs/heads/master") return false if repo_mtime > cache_mtime end true end # Create the cache if it doesn't already exist. def self.ensure_cache generate_cache unless cache_fresh? && cache end # Take a {Report}, as generated by cmd/update.rb. # Unless the cache file exists, do nothing. # If it does exist, but the Report is empty, just touch the cache file. # Otherwise, use the report to update the cache. def self.update_cache(report) return unless CACHE_FILE.exist? if report.empty? FileUtils.touch CACHE_FILE else renamings = report.select_formula(:R) alterations = report.select_formula(:A) + report.select_formula(:M) + renamings.map(&:last) cache_formulae(alterations, save: false) uncache_formulae(report.select_formula(:D) + renamings.map(&:first)) end end # Given an array of formula names, add them and their descriptions to the # cache. Save the updated cache to disk, unless explicitly told not to. def self.cache_formulae(formula_names, options = { save: true }) return unless cache formula_names.each do |name| begin @cache[name] = Formulary.factory(name).desc rescue FormulaUnavailableError, *FormulaVersions::IGNORED_EXCEPTIONS @cache.delete(name) end end save_cache if options[:save] end # Given an array of formula names, remove them and their descriptions from # the cache. Save the updated cache to disk, unless explicitly told not to. def self.uncache_formulae(formula_names, options = { save: true }) return unless cache formula_names.each { |name| @cache.delete(name) } save_cache if options[:save] end # Given a regex, find all formulae whose specified fields contain a match. def self.search(string_or_regex, field = :either) ensure_cache @cache.extend(Searchable) results = case field when :name @cache.search(string_or_regex) { |name, _| name } when :desc @cache.search(string_or_regex) { |_, desc| desc } when :either @cache.search(string_or_regex) end new(results) end # Create an actual instance. def initialize(descriptions) @descriptions = descriptions end # Take search results -- a hash mapping formula names to descriptions -- and # print them. def print blank = Formatter.warning("[no description]") @descriptions.keys.sort.each do |full_name| short_name = short_names[full_name] printed_name = (short_name_counts[short_name] == 1) ? short_name : full_name description = @descriptions[full_name] || blank puts "#{Tty.bold}#{printed_name}:#{Tty.reset} #{description}" end end private def short_names @short_names ||= Hash[@descriptions.keys.map { |k| [k, k.split("/").last] }] end def short_name_counts @short_name_counts ||= short_names.values.each_with_object(Hash.new(0)) { |name, counts| counts[name] += 1 } end end