From 3a9f585ebbd1893a034575b49f64a200c5385365 Mon Sep 17 00:00:00 2001 From: Mike McQuaid Date: Fri, 22 Nov 2019 09:08:31 +0000 Subject: [PATCH] Move more code to utils/analytics. --- Library/Homebrew/cmd/analytics.rb | 27 +- Library/Homebrew/cmd/info.rb | 190 +----------- Library/Homebrew/cmd/update-report.rb | 51 ++-- Library/Homebrew/extend/os/cmd/info.rb | 3 - Library/Homebrew/extend/os/linux/info.rb | 17 -- .../extend/os/linux/utils/analytics.rb | 19 ++ .../extend/os/{ => utils}/analytics.rb | 1 + Library/Homebrew/test/cmd/analytics_spec.rb | 2 +- Library/Homebrew/test/cmd/info_spec.rb | 7 - Library/Homebrew/test/utils/analytics_spec.rb | 7 + Library/Homebrew/utils/analytics.rb | 283 ++++++++++++++++-- 11 files changed, 321 insertions(+), 286 deletions(-) delete mode 100644 Library/Homebrew/extend/os/cmd/info.rb delete mode 100644 Library/Homebrew/extend/os/linux/info.rb create mode 100644 Library/Homebrew/extend/os/linux/utils/analytics.rb rename Library/Homebrew/extend/os/{ => utils}/analytics.rb (59%) diff --git a/Library/Homebrew/cmd/analytics.rb b/Library/Homebrew/cmd/analytics.rb index 9aa663980e..17e225cec4 100644 --- a/Library/Homebrew/cmd/analytics.rb +++ b/Library/Homebrew/cmd/analytics.rb @@ -24,36 +24,23 @@ module Homebrew def analytics analytics_args.parse - config_file = HOMEBREW_REPOSITORY/".git/config" raise UsageError if args.remaining.size > 1 case args.remaining.first when nil, "state" - analyticsdisabled = - Utils.popen_read("git config --file=#{config_file} --get homebrew.analyticsdisabled").chomp - uuid = - Utils.popen_read("git config --file=#{config_file} --get homebrew.analyticsuuid").chomp - if ENV["HOMEBREW_NO_ANALYTICS"] - puts "Analytics is disabled (by HOMEBREW_NO_ANALYTICS)." - elsif analyticsdisabled == "true" - puts "Analytics is disabled." + if Utils::Analytics.disabled? + puts "Analytics are disabled." else - puts "Analytics is enabled." - puts "UUID: #{uuid}" if uuid.present? + puts "Analytics are enabled." + puts "UUID: #{Utils::Analytics.uuid}" if Utils::Analytics.uuid.present? end when "on" - safe_system "git", "config", "--file=#{config_file}", - "--replace-all", "homebrew.analyticsdisabled", "false" - safe_system "git", "config", "--file=#{config_file}", - "--replace-all", "homebrew.analyticsmessage", "true" + Utils::Analytics.enable! when "off" - safe_system "git", "config", "--file=#{config_file}", - "--replace-all", "homebrew.analyticsdisabled", "true" - system "git", "config", "--file=#{config_file}", "--unset-all", "homebrew.analyticsuuid" + Utils::Analytics.disable! when "regenerate-uuid" - # it will be regenerated in next run. - system "git", "config", "--file=#{config_file}", "--unset-all", "homebrew.analyticsuuid" + Utils::Analytics.regenerate_uuid! else raise UsageError end diff --git a/Library/Homebrew/cmd/info.rb b/Library/Homebrew/cmd/info.rb index 142e2fb20c..ad04e74dac 100644 --- a/Library/Homebrew/cmd/info.rb +++ b/Library/Homebrew/cmd/info.rb @@ -89,7 +89,7 @@ module Homebrew def print_info if ARGV.named.empty? if args.analytics? - output_analytics + Utils::Analytics.output elsif HOMEBREW_CELLAR.exist? count = Formula.racks.length puts "#{count} #{"keg".pluralize(count)}, #{HOMEBREW_CELLAR.dup.abv}" @@ -104,13 +104,13 @@ module Homebrew Formulary.find_with_priority(f) end if args.analytics? - output_formula_analytics(formula) + Utils::Analytics.formula_output(formula) else info_formula(formula) end rescue FormulaUnavailableError => e if args.analytics? - output_analytics(filter: f) + Utils::Analytics.output(filter: f) next end ofail e.message @@ -234,169 +234,7 @@ module Homebrew caveats = Caveats.new(f) ohai "Caveats", caveats.to_s unless caveats.empty? - output_formula_analytics(f) - end - - def formulae_api_json(endpoint) - return if ENV["HOMEBREW_NO_ANALYTICS"] || ENV["HOMEBREW_NO_GITHUB_API"] - - output, = curl_output("--max-time", "5", - "https://formulae.brew.sh/api/#{endpoint}") - return if output.blank? - - JSON.parse(output) - rescue JSON::ParserError - nil - end - - def analytics_table(category, days, results, os_version: false, cask_install: 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" - elsif cask_install - "Token" - else - "Name (with options)" - end - - 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(filter: nil) - days = args.days || "30" - category = args.category || "install" - json = formulae_api_json("analytics/#{category}/#{days}d.json") - return if json.blank? || json["items"].blank? - - os_version = category == "os-version" - cask_install = category == "cask-install" - results = {} - json["items"].each do |item| - key = if os_version - item["os_version"] - elsif cask_install - item["cask"] - else - item["formula"] - end - if filter.present? - next if key != filter && !key.start_with?("#{filter} ") - end - results[key] = item["count"].tr(",", "").to_i - end - - if filter.present? && results.blank? - onoe "No results matching `#{filter}` found!" - return - end - - analytics_table(category, days, results, os_version: os_version, cask_install: cask_install) - end - - def output_formula_analytics(f) - json = formulae_api_json("#{formula_path}/#{f}.json") - return if json.blank? || json["analytics"].blank? - - full_analytics = args.analytics? || args.verbose? - - ohai "Analytics" - json["analytics"].each do |category, value| - category = category.tr("_", "-") - analytics = [] - - value.each do |days, results| - days = days.to_i - if full_analytics - if args.days.present? - next if args.days&.to_i != days - end - if args.category.present? - next if args.category != category - end - - analytics_table(category, days, results) - else - total_count = results.values.inject("+") - analytics << "#{number_readable(total_count)} (#{days} days)" - end - end - - puts "#{category}: #{analytics.join(", ")}" unless full_analytics - end + Utils::Analytics.formula_output(f) end def decorate_dependencies(dependencies) @@ -423,24 +261,4 @@ 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: percent) - end - - def formula_path - "formula" - end - alias_method :generic_formula_path, :formula_path - - def analytics_path - "analytics" - end - alias_method :generic_analytics_path, :analytics_path - - require "extend/os/cmd/info" end diff --git a/Library/Homebrew/cmd/update-report.rb b/Library/Homebrew/cmd/update-report.rb index 9a2222f3da..f247e034db 100644 --- a/Library/Homebrew/cmd/update-report.rb +++ b/Library/Homebrew/cmd/update-report.rb @@ -38,38 +38,27 @@ module Homebrew def update_report update_report_args.parse + if !Utils::Analytics.messages_displayed? && + !Utils::Analytics.disabled? && + !Utils::Analytics.no_message_output? + + ENV["HOMEBREW_NO_ANALYTICS_THIS_RUN"] = "1" + # Use the shell's audible bell. + print "\a" + + # Use an extra newline and bold to avoid this being missed. + ohai "Homebrew has enabled anonymous aggregate formulae and cask analytics." + puts <<~EOS + #{Tty.bold}Read the analytics documentation (and how to opt-out) here: + #{Formatter.url("https://docs.brew.sh/Analytics")}#{Tty.reset} + + EOS + + # Consider the messages possibly missed if not a TTY. + Utils::Analytics.messages_displayed! if $stdout.tty? + end + HOMEBREW_REPOSITORY.cd do - analytics_message_displayed = - Utils.popen_read("git", "config", "--get", "homebrew.analyticsmessage").chomp == "true" - cask_analytics_message_displayed = - Utils.popen_read("git", "config", "--get", "homebrew.caskanalyticsmessage").chomp == "true" - analytics_disabled = - Utils.popen_read("git", "config", "--get", "homebrew.analyticsdisabled").chomp == "true" - if !analytics_message_displayed && - !cask_analytics_message_displayed && - !analytics_disabled && - !ENV["HOMEBREW_NO_ANALYTICS"] && - !ENV["HOMEBREW_NO_ANALYTICS_MESSAGE_OUTPUT"] - - ENV["HOMEBREW_NO_ANALYTICS_THIS_RUN"] = "1" - # Use the shell's audible bell. - print "\a" - - # Use an extra newline and bold to avoid this being missed. - ohai "Homebrew has enabled anonymous aggregate formulae and cask analytics." - puts <<~EOS - #{Tty.bold}Read the analytics documentation (and how to opt-out) here: - #{Formatter.url("https://docs.brew.sh/Analytics")}#{Tty.reset} - - EOS - - # Consider the message possibly missed if not a TTY. - if $stdout.tty? - safe_system "git", "config", "--replace-all", "homebrew.analyticsmessage", "true" - safe_system "git", "config", "--replace-all", "homebrew.caskanalyticsmessage", "true" - end - end - donation_message_displayed = Utils.popen_read("git", "config", "--get", "homebrew.donationmessage").chomp == "true" unless donation_message_displayed diff --git a/Library/Homebrew/extend/os/cmd/info.rb b/Library/Homebrew/extend/os/cmd/info.rb deleted file mode 100644 index b20d91f5e9..0000000000 --- a/Library/Homebrew/extend/os/cmd/info.rb +++ /dev/null @@ -1,3 +0,0 @@ -# frozen_string_literal: true - -require "extend/os/linux/info" if OS.linux? diff --git a/Library/Homebrew/extend/os/linux/info.rb b/Library/Homebrew/extend/os/linux/info.rb deleted file mode 100644 index 00bef2b7d6..0000000000 --- a/Library/Homebrew/extend/os/linux/info.rb +++ /dev/null @@ -1,17 +0,0 @@ -# frozen_string_literal: true - -module Homebrew - module_function - - def formula_path - return generic_formula_path if ENV["HOMEBREW_FORCE_HOMEBREW_ON_LINUX"] - - "formula-linux" - end - - def analytics_path - return generic_analytics_path if ENV["HOMEBREW_FORCE_HOMEBREW_ON_LINUX"] - - "analytics-linux" - end -end diff --git a/Library/Homebrew/extend/os/linux/utils/analytics.rb b/Library/Homebrew/extend/os/linux/utils/analytics.rb new file mode 100644 index 0000000000..67e33b2dd6 --- /dev/null +++ b/Library/Homebrew/extend/os/linux/utils/analytics.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +module Utils + module Analytics + class << self + def formula_path + return generic_formula_path if ENV["HOMEBREW_FORCE_HOMEBREW_ON_LINUX"] + + "formula-linux" + end + + def analytics_path + return generic_analytics_path if ENV["HOMEBREW_FORCE_HOMEBREW_ON_LINUX"] + + "analytics-linux" + end + end + end +end diff --git a/Library/Homebrew/extend/os/analytics.rb b/Library/Homebrew/extend/os/utils/analytics.rb similarity index 59% rename from Library/Homebrew/extend/os/analytics.rb rename to Library/Homebrew/extend/os/utils/analytics.rb index 749d80c66d..8598bbf462 100644 --- a/Library/Homebrew/extend/os/analytics.rb +++ b/Library/Homebrew/extend/os/utils/analytics.rb @@ -1,3 +1,4 @@ # frozen_string_literal: true +require "extend/os/linux/utils/analytics" if OS.linux? require "extend/os/mac/utils/analytics" if OS.mac? diff --git a/Library/Homebrew/test/cmd/analytics_spec.rb b/Library/Homebrew/test/cmd/analytics_spec.rb index 82545513bc..4c15cbe966 100644 --- a/Library/Homebrew/test/cmd/analytics_spec.rb +++ b/Library/Homebrew/test/cmd/analytics_spec.rb @@ -14,7 +14,7 @@ describe "brew analytics", :integration_test do brew "analytics", "off" expect { brew "analytics", "HOMEBREW_NO_ANALYTICS" => nil } - .to output(/Analytics is disabled/).to_stdout + .to output(/Analytics are disabled/).to_stdout .and not_to_output.to_stderr .and be_a_success end diff --git a/Library/Homebrew/test/cmd/info_spec.rb b/Library/Homebrew/test/cmd/info_spec.rb index acb131e839..a4c64ca35d 100644 --- a/Library/Homebrew/test/cmd/info_spec.rb +++ b/Library/Homebrew/test/cmd/info_spec.rb @@ -22,13 +22,6 @@ 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/utils/analytics_spec.rb b/Library/Homebrew/test/utils/analytics_spec.rb index 8827c80125..cca36c8360 100644 --- a/Library/Homebrew/test/utils/analytics_spec.rb +++ b/Library/Homebrew/test/utils/analytics_spec.rb @@ -86,4 +86,11 @@ describe Utils::Analytics do end end end + + specify "::table_output" do + results = { ack: 10, wget: 100 } + expect { described_class.table_output("install", "30", results) } + .to output(/110 | 100.00%/).to_stdout + .and not_to_output.to_stderr + end end diff --git a/Library/Homebrew/utils/analytics.rb b/Library/Homebrew/utils/analytics.rb index b5ccf23fb7..764611befe 100644 --- a/Library/Homebrew/utils/analytics.rb +++ b/Library/Homebrew/utils/analytics.rb @@ -5,27 +5,8 @@ require "erb" module Utils module Analytics class << self - def custom_prefix_label - "custom-prefix" - end - - def clear_os_prefix_ci - return unless instance_variable_defined?(:@os_prefix_ci) - - remove_instance_variable(:@os_prefix_ci) - end - - def os_prefix_ci - @os_prefix_ci ||= begin - os = OS_VERSION - prefix = ", #{custom_prefix_label}" unless Homebrew.default_prefix? - ci = ", CI" if ENV["CI"] - "#{os}#{prefix}#{ci}" - end - end - def report(type, metadata = {}) - return if ENV["HOMEBREW_NO_ANALYTICS"] || ENV["HOMEBREW_NO_ANALYTICS_THIS_RUN"] + return if disabled? args = [] @@ -90,8 +71,268 @@ module Utils end report_event("BuildError", action) end + + def messages_displayed? + config_true?(:analyticsmessage) && config_true?(:caskanalyticsmessage) + end + + def disabled? + return true if ENV["HOMEBREW_NO_ANALYTICS"] || ENV["HOMEBREW_NO_ANALYTICS_THIS_RUN"] + + config_true?(:analyticsdisabled) + end + + def no_message_output? + # Used by Homebrew/install + ENV["HOMEBREW_NO_ANALYTICS_MESSAGE_OUTPUT"].present? + end + + def uuid + config_get(:analyticsuuid) + end + + def messages_displayed! + config_set(:analyticsmessage, true) + config_set(:caskanalyticsmessage, true) + end + + def enable! + config_set(:analyticsdisabled, false) + messages_displayed! + end + + def disable! + config_set(:analyticsdisabled, true) + regenerate_uuid! + end + + def regenerate_uuid! + # it will be regenerated in next run unless disabled. + config_delete(:analyticsuuid) + end + + def output(filter: nil) + days = Homebrew.args.days || "30" + category = Homebrew.args.category || "install" + json = formulae_api_json("analytics/#{category}/#{days}d.json") + return if json.blank? || json["items"].blank? + + os_version = category == "os-version" + cask_install = category == "cask-install" + results = {} + json["items"].each do |item| + key = if os_version + item["os_version"] + elsif cask_install + item["cask"] + else + item["formula"] + end + if filter.present? + next if key != filter && !key.start_with?("#{filter} ") + end + results[key] = item["count"].tr(",", "").to_i + end + + if filter.present? && results.blank? + onoe "No results matching `#{filter}` found!" + return + end + + table_output(category, days, results, os_version: os_version, cask_install: cask_install) + end + + def formula_output(f) + json = formulae_api_json("#{formula_path}/#{f}.json") + return if json.blank? || json["analytics"].blank? + + full_analytics = Homebrew.args.analytics? || Homebrew.args.verbose? + + ohai "Analytics" + json["analytics"].each do |category, value| + category = category.tr("_", "-") + analytics = [] + + value.each do |days, results| + days = days.to_i + if full_analytics + if Homebrew.args.days.present? + next if Homebrew.args.days&.to_i != days + end + if Homebrew.args.category.present? + next if Homebrew.args.category != category + end + + analytics_table(category, days, results) + else + total_count = results.values.inject("+") + analytics << "#{number_readable(total_count)} (#{days} days)" + end + end + + puts "#{category}: #{analytics.join(", ")}" unless full_analytics + end + end + + def custom_prefix_label + "custom-prefix" + end + + def clear_os_prefix_ci + return unless instance_variable_defined?(:@os_prefix_ci) + + remove_instance_variable(:@os_prefix_ci) + end + + def os_prefix_ci + @os_prefix_ci ||= begin + os = OS_VERSION + prefix = ", #{custom_prefix_label}" unless Homebrew.default_prefix? + ci = ", CI" if ENV["CI"] + "#{os}#{prefix}#{ci}" + end + end + + def table_output(category, days, results, os_version: false, cask_install: 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" + elsif cask_install + "Token" + else + "Name (with options)" + end + + 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 config_true?(key) + config_get(key) == "true" + end + + def config_get(key) + HOMEBREW_REPOSITORY.cd do + Utils.popen_read("git", "config", "--get", "homebrew.#{key}").chomp + end + end + + def config_set(key, value) + HOMEBREW_REPOSITORY.cd do + safe_system "git", "config", "--replace-all", "homebrew.#{key}", value.to_s + end + end + + def config_delete(key) + HOMEBREW_REPOSITORY.cd do + system "git", "config", "--unset-all", "homebrew.#{key}" + end + end + + def formulae_api_json(endpoint) + return if ENV["HOMEBREW_NO_ANALYTICS"] || ENV["HOMEBREW_NO_GITHUB_API"] + + output, = curl_output("--max-time", "5", + "https://formulae.brew.sh/api/#{endpoint}") + return if output.blank? + + JSON.parse(output) + rescue JSON::ParserError + nil + end + + def format_count(count) + count.to_s.reverse.gsub(/(\d{3})(?=\d)/, '\\1,').reverse + end + + def format_percent(percent) + format("%.2f", percent: percent) + end + + def formula_path + "formula" + end + alias generic_formula_path formula_path + + def analytics_path + "analytics" + end + alias generic_analytics_path analytics_path end end end -require "extend/os/analytics" +require "extend/os/utils/analytics"