diff --git a/Library/Homebrew/cmd/info.rb b/Library/Homebrew/cmd/info.rb index be8398bdb5..920337a905 100644 --- a/Library/Homebrew/cmd/info.rb +++ b/Library/Homebrew/cmd/info.rb @@ -1,11 +1,20 @@ #: * `info`: #: Display brief statistics for your Homebrew installation. #: -#: * `info` [`--verbose`]: +#: * `info` `--analytics` [`--days=`] [`--category=`]: +#: Display Homebrew analytics data (provided neither `HOMEBREW_NO_ANALYTICS` +#: or `HOMEBREW_NO_GITHUB_API` are set) +#: +#: The value for `days` must be `30`, `90` or `365`. The default is `30`. +#: +#: The value for `category` must be `install`, `install-on-request`, +#: `build-error` or `os-version`. The default is `install`. +#: +#: * `info` [`--analytics`]: #: Display information about and analytics data (provided neither #: `HOMEBREW_NO_ANALYTICS` or `HOMEBREW_NO_GITHUB_API` are set) #: -#: Pass `--verbose` to see more detailed analytics data. +#: Pass `--analytics` to see more detailed analytics data. #: #: * `info` `--github` : #: Open a browser to the GitHub History page for . @@ -47,7 +56,9 @@ module Homebrew def print_info if ARGV.named.empty? - if HOMEBREW_CELLAR.exist? + if ARGV.include?("--analytics") + output_analytics + elsif HOMEBREW_CELLAR.exist? count = Formula.racks.length puts "#{count} #{"keg".pluralize(count)}, #{HOMEBREW_CELLAR.abv}" end @@ -184,42 +195,156 @@ module Homebrew caveats = Caveats.new(f) ohai "Caveats", caveats.to_s unless caveats.empty? - output_analytics(f) + output_formula_analytics(f) end - def output_analytics(f) - return if ENV["HOMEBREW_NO_ANALYTICS"] - return if ENV["HOMEBREW_NO_GITHUB_API"] + def formulae_api_json(endpoint) + return if ENV["HOMEBREW_NO_ANALYTICS"] || ENV["HOMEBREW_NO_GITHUB_API"] - formulae_json_url = "https://formulae.brew.sh/api/formula/#{f}.json" - output, = curl_output("--max-time", "3", formulae_json_url) - return if output.empty? + output, = curl_output("--max-time", "3", + "https://formulae.brew.sh/api/#{endpoint}") + return if output.blank? - json = begin - JSON.parse(output) - rescue JSON::ParserError - nil + JSON.parse(output) + rescue JSON::ParserError + nil + end + + def analytics_table(category, days, results, os_version: false) + oh1 "#{category} (#{days} days)" + total_count = results.values.inject("+") + formatted_total_count = format_count(total_count) + formatted_total_percent = format_percent(100) + + index_header = "Index" + count_header = "Count" + percent_header = "Percent" + name_with_options_header = if os_version + "macOS Version" + else + "Name (with options)" end - return if json.nil? || json.empty? || json["analytics"].empty? + + total_index_footer = "Total" + max_index_width = results.length.to_s.length + index_width = [ + index_header.length, + total_index_footer.length, + max_index_width, + ].max + count_width = [ + count_header.length, + formatted_total_count.length, + ].max + percent_width = [ + percent_header.length, + formatted_total_percent.length, + ].max + name_with_options_width = Tty.width - + index_width - + count_width - + percent_width - + 10 # spacing and lines + + formatted_index_header = + format "%#{index_width}s", index_header + formatted_name_with_options_header = + format "%-#{name_with_options_width}s", + name_with_options_header[0..name_with_options_width-1] + formatted_count_header = + format "%#{count_width}s", count_header + formatted_percent_header = + format "%#{percent_width}s", percent_header + puts "#{formatted_index_header} | #{formatted_name_with_options_header} | "\ + "#{formatted_count_header} | #{formatted_percent_header}" + + columns_line = "#{"-"*index_width}:|-#{"-"*name_with_options_width}-|-"\ + "#{"-"*count_width}:|-#{"-"*percent_width}:" + puts columns_line + + index = 0 + results.each do |name_with_options, count| + index += 1 + formatted_index = format "%0#{max_index_width}d", index + formatted_index = format "%-#{index_width}s", formatted_index + formatted_name_with_options = + format "%-#{name_with_options_width}s", + name_with_options[0..name_with_options_width-1] + formatted_count = format "%#{count_width}s", format_count(count) + formatted_percent = if total_count.zero? + format "%#{percent_width}s", format_percent(0) + else + format "%#{percent_width}s", + format_percent((count.to_i * 100) / total_count.to_f) + end + puts "#{formatted_index} | #{formatted_name_with_options} | " \ + "#{formatted_count} | #{formatted_percent}%" + next if index > 10 + end + return unless results.length > 1 + + formatted_total_footer = + format "%-#{index_width}s", total_index_footer + formatted_blank_footer = + format "%-#{name_with_options_width}s", "" + formatted_total_count_footer = + format "%#{count_width}s", formatted_total_count + formatted_total_percent_footer = + format "%#{percent_width}s", formatted_total_percent + puts "#{formatted_total_footer} | #{formatted_blank_footer} | "\ + "#{formatted_total_count_footer} | #{formatted_total_percent_footer}%" + end + + def output_analytics + days = ARGV.value("days") || "30" + valid_days = %w[30 90 365] + unless valid_days.include?(days) + raise ArgumentError("Days must be one of #{valid_days.join(", ")}!") + end + + category = ARGV.value("category") || "install" + valid_categories = %w[install install-on-request build-error os-version] + unless valid_categories.include?(category) + raise ArgumentError("Categories must be one of #{valid_categories.join(", ")}") + end + + json = formulae_api_json("analytics/#{category}/#{days}d.json") + return if json.blank? || json["items"].blank? + + os_version = category == "os-version" + results = {} + json["items"].each do |item| + key = if os_version + item["os_version"] + else + item["formula"] + end + results[key] = item["count"].tr(",", "").to_i + end + analytics_table(category, days, results, os_version: os_version) + end + + def output_formula_analytics(f) + json = formulae_api_json("formula/#{f}.json") + return if json.blank? || json["analytics"].blank? + + full_analytics = ARGV.include?("--analytics") || ARGV.verbose? ohai "Analytics" - if ARGV.verbose? - json["analytics"].each do |category, value| - value.each do |range, results| - oh1 "#{category} (#{range})" - results.each do |name_with_options, count| - puts "#{name_with_options}: #{number_readable(count)}" - end + json["analytics"].each do |category, value| + analytics = [] + + value.each do |days, results| + days = days.to_i + if full_analytics + analytics_table(category, days, results) + else + total_count = results.values.inject("+") + analytics << "#{number_readable(total_count)} (#{days} days)" end end - return - end - json["analytics"].each do |category, value| - analytics = value.map do |range, results| - "#{number_readable(results.values.inject("+"))} (#{range})" - end - puts "#{category}: #{analytics.join(", ")}" + puts "#{category}: #{analytics.join(", ")}" unless full_analytics end end @@ -247,4 +372,12 @@ module Homebrew "#{dep.name} #{dep.option_tags.map { |o| "--#{o}" }.join(" ")}" end + + def format_count(count) + count.to_s.reverse.gsub(/(\d{3})(?=\d)/, '\\1,').reverse + end + + def format_percent(percent) + format "%.2f", percent + end end diff --git a/Library/Homebrew/test/cmd/info_spec.rb b/Library/Homebrew/test/cmd/info_spec.rb index 2d091de2ed..b318576e63 100644 --- a/Library/Homebrew/test/cmd/info_spec.rb +++ b/Library/Homebrew/test/cmd/info_spec.rb @@ -23,6 +23,13 @@ end describe Homebrew do let(:remote) { "https://github.com/Homebrew/homebrew-core" } + specify "::analytics_table" do + results = { ack: 10, wget: 100 } + expect { subject.analytics_table("install", "30", results) } + .to output(/110 | 100.00%/).to_stdout + .and not_to_output.to_stderr + end + specify "::github_remote_path" do expect(subject.github_remote_path(remote, "Formula/git.rb")) .to eq("https://github.com/Homebrew/homebrew-core/blob/master/Formula/git.rb") diff --git a/Library/Homebrew/test/spec_helper.rb b/Library/Homebrew/test/spec_helper.rb index 9d30c1e7db..83afadad91 100644 --- a/Library/Homebrew/test/spec_helper.rb +++ b/Library/Homebrew/test/spec_helper.rb @@ -187,7 +187,7 @@ RSpec::Matchers.define :a_json_string do begin JSON.parse(actual) true - rescue JSON::ParseError + rescue JSON::ParserError false end end diff --git a/docs/Manpage.md b/docs/Manpage.md index 764f348791..eb50bd1cab 100644 --- a/docs/Manpage.md +++ b/docs/Manpage.md @@ -199,11 +199,20 @@ With `--verbose` or `-v`, many commands print extra debugging information. Note * `info`: Display brief statistics for your Homebrew installation. - * `info` *`formula`* [`--verbose`]: + * `info` `--analytics` [`--days=`*`days`*] [`--category=`*`category`*]: + Display Homebrew analytics data (provided neither `HOMEBREW_NO_ANALYTICS` + or `HOMEBREW_NO_GITHUB_API` are set) + + The value for `days` must be `30`, `90` or `365`. The default is `30`. + + The value for `category` must be `install`, `install-on-request`, + `build-error` or `os-version`. The default is `install`. + + * `info` *`formula`* [`--analytics`]: Display information about *`formula`* and analytics data (provided neither `HOMEBREW_NO_ANALYTICS` or `HOMEBREW_NO_GITHUB_API` are set) - Pass `--verbose` to see more detailed analytics data. + Pass `--analytics` to see more detailed analytics data. * `info` `--github` *`formula`*: Open a browser to the GitHub History page for *`formula`*. diff --git a/manpages/brew.1 b/manpages/brew.1 index af285a9e91..5f371030c1 100644 --- a/manpages/brew.1 +++ b/manpages/brew.1 @@ -208,11 +208,21 @@ Open \fIformula\fR\'s homepage in a browser\. Display brief statistics for your Homebrew installation\. . .TP -\fBinfo\fR \fIformula\fR [\fB\-\-verbose\fR] +\fBinfo\fR \fB\-\-analytics\fR [\fB\-\-days=\fR\fIdays\fR] [\fB\-\-category=\fR\fIcategory\fR] +Display Homebrew analytics data (provided neither \fBHOMEBREW_NO_ANALYTICS\fR or \fBHOMEBREW_NO_GITHUB_API\fR are set) +. +.IP +The value for \fBdays\fR must be \fB30\fR, \fB90\fR or \fB365\fR\. The default is \fB30\fR\. +. +.IP +The value for \fBcategory\fR must be \fBinstall\fR, \fBinstall\-on\-request\fR, \fBbuild\-error\fR or \fBos\-version\fR\. The default is \fBinstall\fR\. +. +.TP +\fBinfo\fR \fIformula\fR [\fB\-\-analytics\fR] Display information about \fIformula\fR and analytics data (provided neither \fBHOMEBREW_NO_ANALYTICS\fR or \fBHOMEBREW_NO_GITHUB_API\fR are set) . .IP -Pass \fB\-\-verbose\fR to see more detailed analytics data\. +Pass \fB\-\-analytics\fR to see more detailed analytics data\. . .TP \fBinfo\fR \fB\-\-github\fR \fIformula\fR