aboutsummaryrefslogtreecommitdiffstats
path: root/Library
diff options
context:
space:
mode:
authorDan Martinez2015-05-05 15:29:01 -0700
committerMike McQuaid2015-09-08 15:23:37 +0100
commit837437416840d4c4f132b7e4ba2ea0fa2f861668 (patch)
treea05615c66686eb93cf7cea2b5d8eed66e82be4f2 /Library
parentc4ceaabfc19bedc70be53f2df2be7546bb9286e8 (diff)
downloadbrew-837437416840d4c4f132b7e4ba2ea0fa2f861668.tar.bz2
Improve description searching and add a cache.
Closes Homebrew/homebrew#42281. Signed-off-by: Mike McQuaid <mike@mikemcquaid.com>
Diffstat (limited to 'Library')
-rw-r--r--Library/Contributions/brew_bash_completion.sh13
-rw-r--r--Library/Contributions/brew_zsh_completion.zsh3
-rw-r--r--Library/Homebrew/cmd/desc.rb40
-rw-r--r--Library/Homebrew/cmd/search.rb7
-rw-r--r--Library/Homebrew/cmd/tap.rb2
-rw-r--r--Library/Homebrew/cmd/untap.rb2
-rw-r--r--Library/Homebrew/cmd/update.rb2
-rw-r--r--Library/Homebrew/descriptions.rb151
-rw-r--r--Library/Homebrew/manpages/brew.1.md10
-rw-r--r--Library/Homebrew/utils.rb4
10 files changed, 226 insertions, 8 deletions
diff --git a/Library/Contributions/brew_bash_completion.sh b/Library/Contributions/brew_bash_completion.sh
index 5c2992126..83087ea54 100644
--- a/Library/Contributions/brew_bash_completion.sh
+++ b/Library/Contributions/brew_bash_completion.sh
@@ -195,6 +195,18 @@ _brew_deps ()
__brew_complete_formulae
}
+_brew_desc ()
+{
+ local cur="${COMP_WORDS[COMP_CWORD]}"
+ case "$cur" in
+ --*)
+ __brewcomp "--search --name --description"
+ return
+ ;;
+ esac
+ __brew_complete_formulae
+}
+
_brew_doctor () {
local cur="${COMP_WORDS[COMP_CWORD]}"
__brewcomp "$(brew doctor --list-checks)"
@@ -599,6 +611,7 @@ _brew ()
cleanup) _brew_cleanup ;;
create) _brew_create ;;
deps) _brew_deps ;;
+ desc) _brew_desc ;;
doctor|dr) _brew_doctor ;;
diy|configure) _brew_diy ;;
fetch) _brew_fetch ;;
diff --git a/Library/Contributions/brew_zsh_completion.zsh b/Library/Contributions/brew_zsh_completion.zsh
index dec63b84d..27faa339a 100644
--- a/Library/Contributions/brew_zsh_completion.zsh
+++ b/Library/Contributions/brew_zsh_completion.zsh
@@ -40,6 +40,7 @@ _1st_arguments=(
'config:show homebrew and system configuration'
'create:create a new formula'
'deps:list dependencies and dependants of a formula'
+ 'desc:display a description of a formula'
'doctor:audits your installation for common issues'
'edit:edit a formula'
'fetch:download formula resources to the cache'
@@ -95,7 +96,7 @@ if (( CURRENT == 1 )); then
fi
case "$words[1]" in
- install|reinstall|audit|home|homepage|log|info|abv|uses|cat|deps|edit|options|switch)
+ install|reinstall|audit|home|homepage|log|info|abv|uses|cat|deps|desc|edit|options|switch)
_brew_all_formulae
_wanted formulae expl 'all formulae' compadd -a formulae ;;
list|ls)
diff --git a/Library/Homebrew/cmd/desc.rb b/Library/Homebrew/cmd/desc.rb
new file mode 100644
index 000000000..77078e8f5
--- /dev/null
+++ b/Library/Homebrew/cmd/desc.rb
@@ -0,0 +1,40 @@
+require "descriptions"
+require "cmd/search"
+
+module Homebrew
+ def desc
+ if ARGV.options_only.empty?
+ if ARGV.named.empty?
+ raise FormulaUnspecifiedError
+ exit
+ end
+ results = Descriptions.named(ARGV.formulae.map(&:full_name))
+ else
+ if ARGV.options_only.count != 1
+ odie "Pick one, and only one, of -s/--search, -n/--name, or -d/--description."
+ end
+
+ search_arg = ARGV.options_only.first
+
+ search_type = case search_arg
+ when '-s', '--search'
+ :either
+ when '-n', '--name'
+ :name
+ when '-d', '--description'
+ :desc
+ else
+ odie "Unrecognized option '#{search_arg}'."
+ end
+
+ if arg = ARGV.named.first
+ regex = Homebrew::query_regexp(arg)
+ results = Descriptions.search(regex, search_type)
+ else
+ odie "You must provide a search term."
+ end
+ end
+
+ results.print unless results.nil?
+ end
+end
diff --git a/Library/Homebrew/cmd/search.rb b/Library/Homebrew/cmd/search.rb
index 10e0a8269..9e5f28815 100644
--- a/Library/Homebrew/cmd/search.rb
+++ b/Library/Homebrew/cmd/search.rb
@@ -3,6 +3,7 @@ require "blacklist"
require "utils"
require "thread"
require "official_taps"
+require 'descriptions'
module Homebrew
SEARCH_ERROR_QUEUE = Queue.new
@@ -23,11 +24,7 @@ module Homebrew
elsif ARGV.include? "--desc"
query = ARGV.next
rx = query_regexp(query)
- Formula.each do |formula|
- if formula.desc =~ rx
- puts "#{Tty.white}#{formula.full_name}:#{Tty.reset} #{formula.desc}"
- end
- end
+ Descriptions.search(rx, :desc).print
elsif ARGV.empty?
puts_columns Formula.full_names
elsif ARGV.first =~ HOMEBREW_TAP_FORMULA_REGEX
diff --git a/Library/Homebrew/cmd/tap.rb b/Library/Homebrew/cmd/tap.rb
index 0c8960cbd..291407aff 100644
--- a/Library/Homebrew/cmd/tap.rb
+++ b/Library/Homebrew/cmd/tap.rb
@@ -1,4 +1,5 @@
require "tap"
+require "descriptions"
module Homebrew
def tap
@@ -41,6 +42,7 @@ module Homebrew
formula_count = tap.formula_files.size
puts "Tapped #{formula_count} formula#{plural(formula_count, "e")} (#{tap.path.abv})"
+ Descriptions.cache_formulae(tap.formula_names)
if !clone_target && tap.private?
puts <<-EOS.undent
diff --git a/Library/Homebrew/cmd/untap.rb b/Library/Homebrew/cmd/untap.rb
index 1e8bfdcab..22dab7383 100644
--- a/Library/Homebrew/cmd/untap.rb
+++ b/Library/Homebrew/cmd/untap.rb
@@ -1,4 +1,5 @@
require "cmd/tap" # for tap_args
+require "descriptions"
module Homebrew
def untap
@@ -13,6 +14,7 @@ module Homebrew
tap.unpin if tap.pinned?
formula_count = tap.formula_files.size
+ Descriptions.uncache_formulae(tap.formula_names)
tap.path.rmtree
tap.path.dirname.rmdir_if_possible
puts "Untapped #{formula_count} formula#{plural(formula_count, "e")}"
diff --git a/Library/Homebrew/cmd/update.rb b/Library/Homebrew/cmd/update.rb
index 1d4eb5928..4be763b6a 100644
--- a/Library/Homebrew/cmd/update.rb
+++ b/Library/Homebrew/cmd/update.rb
@@ -2,6 +2,7 @@ require "cmd/tap"
require "formula_versions"
require "migrator"
require "formulary"
+require "descriptions"
module Homebrew
def update
@@ -100,6 +101,7 @@ module Homebrew
puts "Updated Homebrew from #{master_updater.initial_revision[0, 8]} to #{master_updater.current_revision[0, 8]}."
report.dump
end
+ Descriptions.update_cache(report)
end
private
diff --git a/Library/Homebrew/descriptions.rb b/Library/Homebrew/descriptions.rb
new file mode 100644
index 000000000..428f42a5e
--- /dev/null
+++ b/Library/Homebrew/descriptions.rb
@@ -0,0 +1,151 @@
+require "formula"
+require "csv"
+
+class Descriptions
+ CACHE_FILE = HOMEBREW_CACHE + "desc_cache"
+
+ def self.cache
+ @cache || self.load_cache
+ end
+
+ # If the cache file exists, load it into, and return, a hash; otherwise,
+ # return nil.
+ def self.load_cache
+ if CACHE_FILE.exist?
+ @cache = {}
+ CSV.foreach(CACHE_FILE) { |name, desc| @cache[name] = desc }
+ @cache
+ end
+ end
+
+ # Write the cache to disk after ensuring the existence of the containing
+ # directory.
+ def self.save_cache
+ HOMEBREW_CACHE.mkpath
+ CSV.open(CACHE_FILE, 'w') do |csv|
+ @cache.each do |name, desc|
+ csv << [name, desc]
+ end
+ end
+ end
+
+ # Create a hash mapping all formulae to their descriptions;
+ # save it for future use.
+ def self.generate_cache
+ @cache = {}
+ Formula.map do |f|
+ @cache[f.full_name] = f.desc
+ end
+ self.save_cache
+ end
+
+ # Return true if the cache exists, and neither Homebrew nor any of the Taps
+ # repos were updated more recently than it was.
+ def self.cache_fresh?
+ if CACHE_FILE.exist?
+ cache_date = File.mtime(CACHE_FILE)
+
+ ref_master = ".git/refs/heads/master"
+ master = HOMEBREW_REPOSITORY/ref_master
+
+ last_update = (master.exist? ? File.mtime(master) : Time.at(0))
+
+ Dir.glob(HOMEBREW_LIBRARY/"Taps/**"/ref_master).each do |repo|
+ repo_mtime = File.mtime(repo)
+ last_update = repo_mtime if repo_mtime > last_update
+ end
+ last_update <= cache_date
+ end
+ end
+
+ # Create the cache if it doesn't already exist.
+ def self.ensure_cache
+ self.generate_cache unless self.cache_fresh? && self.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)
+ if 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)
+ self.cache_formulae(alterations, :save => false)
+ self.uncache_formulae(report.select_formula(:D) +
+ renamings.map(&:first))
+ end
+ 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 })
+ if self.cache
+ formula_names.each { |name| @cache[name] = Formula[name].desc }
+ self.save_cache if options[:save]
+ end
+ 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 })
+ if self.cache
+ formula_names.each { |name| @cache.delete(name) }
+ self.save_cache if options[:save]
+ end
+ end
+
+ # Given an array of formula names, return a {Descriptions} object mapping
+ # those names to their descriptions.
+ def self.named(names)
+ self.ensure_cache
+
+ results = {}
+ unless names.empty?
+ results = names.inject({}) do |accum, name|
+ accum[name] = @cache[name]
+ accum
+ end
+ end
+
+ new(results)
+ end
+
+ # Given a regex, find all formulae whose specified fields contain a match.
+ def self.search(regex, field = :either)
+ self.ensure_cache
+
+ results = case field
+ when :name
+ @cache.select { |name, _| name =~ regex }
+ when :desc
+ @cache.select { |_, desc| desc =~ regex }
+ when :either
+ @cache.select { |name, desc| (name =~ regex) || (desc =~ regex) }
+ end
+
+ results = Hash[results] if RUBY_VERSION <= "1.8.7"
+
+ 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 = "#{Tty.yellow}[no description]#{Tty.reset}"
+ @descriptions.keys.sort.each do |name|
+ description = @descriptions[name] || blank
+ puts "#{Tty.white}#{name}:#{Tty.reset} #{description}"
+ end
+ end
+end
diff --git a/Library/Homebrew/manpages/brew.1.md b/Library/Homebrew/manpages/brew.1.md
index 5adcb2180..06df14196 100644
--- a/Library/Homebrew/manpages/brew.1.md
+++ b/Library/Homebrew/manpages/brew.1.md
@@ -126,6 +126,16 @@ Note that these flags should only appear after a command.
type dependencies, pass `--skip-build`. Similarly, pass `--skip-optional`
to skip `:optional` dependencies.
+ * `desc` <formula>:
+ Display <formula>'s name and one-line description.
+
+ * `desc [-s|-n|-d] <pattern>`:
+ Search both name and description (`-s`), just the names (`-n`), or just the
+ descriptions (`-d`) for `<pattern>`. `<pattern>` is by default interpreted
+ as a literal string; if flanked by slashes, it is instead interpreted as a
+ regular expression. Formula descriptions are cached; the cache is created on
+ the first search, making that search slower than subsequent ones.
+
* `diy [--name=<name>] [--version=<version>]`:
Automatically determine the installation prefix for non-Homebrew software.
diff --git a/Library/Homebrew/utils.rb b/Library/Homebrew/utils.rb
index 2b496565c..bec8861da 100644
--- a/Library/Homebrew/utils.rb
+++ b/Library/Homebrew/utils.rb
@@ -255,8 +255,8 @@ def puts_columns(items, star_items = [])
# determine the best width to display for different console sizes
console_width = `/bin/stty size`.chomp.split(" ").last.to_i
console_width = 80 if console_width <= 0
- longest = items.sort_by(&:length).last
- optimal_col_width = (console_width.to_f / (longest.length + 2).to_f).floor
+ max_len = items.reduce(0) { |max, item| l = item.length ; l > max ? l : max }
+ optimal_col_width = (console_width.to_f / (max_len + 2).to_f).floor
cols = optimal_col_width > 1 ? optimal_col_width : 1
IO.popen("/usr/bin/pr -#{cols} -t -w#{console_width}", "w") { |io| io.puts(items) }