Skip to content
Snippets Groups Projects
Unverified Commit cf293d0e authored by nandahkrishna's avatar nandahkrishna
Browse files

livecheck: progress bar for JSON output

parent e7ab97a8
No related branches found
No related tags found
No related merge requests found
Showing
with 867 additions and 8 deletions
...@@ -130,7 +130,6 @@ ...@@ -130,7 +130,6 @@
**/vendor/bundle/ruby/*/gems/rubocop-0*/ **/vendor/bundle/ruby/*/gems/rubocop-0*/
**/vendor/bundle/ruby/*/gems/rubocop-ast-*/ **/vendor/bundle/ruby/*/gems/rubocop-ast-*/
**/vendor/bundle/ruby/*/gems/ruby-prof-*/ **/vendor/bundle/ruby/*/gems/ruby-prof-*/
**/vendor/bundle/ruby/*/gems/ruby-progressbar-*/
**/vendor/bundle/ruby/*/gems/simplecov-*/ **/vendor/bundle/ruby/*/gems/simplecov-*/
**/vendor/bundle/ruby/*/gems/simplecov-html-*/ **/vendor/bundle/ruby/*/gems/simplecov-html-*/
**/vendor/bundle/ruby/*/gems/sorbet-*/ **/vendor/bundle/ruby/*/gems/sorbet-*/
......
...@@ -26,15 +26,17 @@ module Homebrew ...@@ -26,15 +26,17 @@ module Homebrew
switch "--full-name", switch "--full-name",
description: "Print formulae with fully-qualified names." description: "Print formulae with fully-qualified names."
flag "--tap=", flag "--tap=",
description: "Check the formulae within the given tap, specified as <user>`/`<repo>." description: "Check formulae within the given tap, specified as <user>`/`<repo>."
switch "--installed",
description: "Check formulae that are currently installed."
switch "--json",
description: "Output informations in JSON format."
switch "--all", switch "--all",
description: "Check all available formulae." description: "Check all available formulae."
switch "--installed",
description: "Check formulae that are currently installed."
switch "--newer-only", switch "--newer-only",
description: "Show the latest version only if it's newer than the formula." description: "Show the latest version only if it's newer than the formula."
switch "--json",
description: "Output information in JSON format."
switch "-q", "--quiet",
description: "Suppress warnings, don't print a progress bar for JSON output."
conflicts "--debug", "--json" conflicts "--debug", "--json"
conflicts "--tap=", "--all", "--installed" conflicts "--tap=", "--all", "--installed"
end end
......
# frozen_string_literal: true # frozen_string_literal: true
require "livecheck/strategy" require "livecheck/strategy"
require "ruby-progressbar"
module Homebrew module Homebrew
# The `Livecheck` module consists of methods used by the `brew livecheck` # The `Livecheck` module consists of methods used by the `brew livecheck`
...@@ -66,6 +67,24 @@ module Homebrew ...@@ -66,6 +67,24 @@ module Homebrew
@livecheck_strategy_names.freeze @livecheck_strategy_names.freeze
has_a_newer_upstream_version = false has_a_newer_upstream_version = false
if args.json? && !args.quiet? && $stderr.tty?
total_formulae = if formulae_to_check == Formula
formulae_to_check.count
else
formulae_to_check.length
end
$stderr.puts Formatter.headline("Running checks", color: :blue)
progress = ProgressBar.create(
total: total_formulae,
progress_mark: "#",
remainder_mark: ".",
format: " %t: [%B] %c/%C ",
output: $stderr,
)
end
formulae_checked = formulae_to_check.sort.map.with_index do |formula, i| formulae_checked = formulae_to_check.sort.map.with_index do |formula, i|
if args.debug? && i.positive? if args.debug? && i.positive?
puts <<~EOS puts <<~EOS
...@@ -78,7 +97,7 @@ module Homebrew ...@@ -78,7 +97,7 @@ module Homebrew
skip_result = skip_conditions(formula, args: args) skip_result = skip_conditions(formula, args: args)
next skip_result if skip_result != false next skip_result if skip_result != false
formula.head.downloader.shutup! if formula.head? formula.head&.downloader&.shutup!
current = if formula.head? current = if formula.head?
formula.any_installed_version.version.commit formula.any_installed_version.version.commit
...@@ -136,6 +155,7 @@ module Homebrew ...@@ -136,6 +155,7 @@ module Homebrew
has_a_newer_upstream_version ||= true has_a_newer_upstream_version ||= true
if args.json? if args.json?
progress&.increment
info.except!(:meta) unless args.verbose? info.except!(:meta) unless args.verbose?
next info next info
end end
...@@ -146,6 +166,7 @@ module Homebrew ...@@ -146,6 +166,7 @@ module Homebrew
Homebrew.failed = true Homebrew.failed = true
if args.json? if args.json?
progress&.increment
status_hash(formula, "error", [e.to_s], args: args) status_hash(formula, "error", [e.to_s], args: args)
elsif !args.quiet? elsif !args.quiet?
onoe "#{Tty.blue}#{formula_name(formula, args: args)}#{Tty.reset}: #{e}" onoe "#{Tty.blue}#{formula_name(formula, args: args)}#{Tty.reset}: #{e}"
...@@ -157,7 +178,14 @@ module Homebrew ...@@ -157,7 +178,14 @@ module Homebrew
puts "No newer upstream versions." puts "No newer upstream versions."
end end
puts JSON.generate(formulae_checked.compact) if args.json? return unless args.json?
if progress
progress.finish
$stderr.print "#{Tty.up}#{Tty.erase_line}" * 2
end
puts JSON.generate(formulae_checked.compact)
end end
# Returns the fully-qualified name of a formula if the full_name argument is # Returns the fully-qualified name of a formula if the full_name argument is
......
...@@ -53,6 +53,15 @@ module Tty ...@@ -53,6 +53,15 @@ module Tty
no_underline: 24, no_underline: 24,
}.freeze }.freeze
SPECIAL_CODES = {
up: "1A",
down: "1B",
right: "1C",
left: "1D",
erase_line: "K",
erase_char: "P",
}.freeze
CODES = COLOR_CODES.merge(STYLE_CODES).freeze CODES = COLOR_CODES.merge(STYLE_CODES).freeze
def append_to_escape_sequence(code) def append_to_escape_sequence(code)
...@@ -77,6 +86,16 @@ module Tty ...@@ -77,6 +86,16 @@ module Tty
end end
end end
SPECIAL_CODES.each do |name, code|
define_singleton_method(name) do
if $stdout.tty?
"\033[#{code}"
else
""
end
end
end
def to_s def to_s
return "" unless color? return "" unless color?
......
# frozen_string_literal: true
require 'ruby-progressbar/output'
require 'ruby-progressbar/outputs/tty'
require 'ruby-progressbar/outputs/non_tty'
require 'ruby-progressbar/timer'
require 'ruby-progressbar/progress'
require 'ruby-progressbar/throttle'
require 'ruby-progressbar/calculators/length'
require 'ruby-progressbar/calculators/running_average'
require 'ruby-progressbar/components'
require 'ruby-progressbar/format'
require 'ruby-progressbar/base'
require 'ruby-progressbar/refinements' if Module.
private_instance_methods.
include?(:using)
class ProgressBar
def self.create(*args)
ProgressBar::Base.new(*args)
end
end
require 'forwardable'
class ProgressBar
class Base
extend Forwardable
def_delegators :output,
:clear,
:log,
:refresh
def_delegators :progressable,
:progress,
:total
def initialize(options = {})
self.autostart = options.fetch(:autostart, true)
self.autofinish = options.fetch(:autofinish, true)
self.finished = false
self.timer = Timer.new(options)
self.progressable = Progress.new(options)
options = options.merge(:timer => timer,
:progress => progressable)
self.title_comp = Components::Title.new(options)
self.bar = Components::Bar.new(options)
self.percentage = Components::Percentage.new(options)
self.rate = Components::Rate.new(options)
self.time = Components::Time.new(options)
self.output = Output.detect(options.merge(:bar => self))
@format = Format::String.new(output.resolve_format(options[:format]))
start :at => options[:starting_at] if autostart
end
def start(options = {})
timer.start
update_progress(:start, options)
end
def finish
return if finished?
output.with_refresh do
self.finished = true
progressable.finish
timer.stop
end
end
def pause
output.with_refresh { timer.pause } unless paused?
end
def stop
output.with_refresh { timer.stop } unless stopped?
end
def resume
output.with_refresh { timer.resume } if stopped?
end
def reset
output.with_refresh do
self.finished = false
progressable.reset
timer.reset
end
end
def stopped?
timer.stopped? || finished?
end
alias paused? stopped?
def finished?
finished || (autofinish && progressable.finished?)
end
def started?
timer.started?
end
def decrement
update_progress(:decrement)
end
def increment
update_progress(:increment)
end
def progress=(new_progress)
update_progress(:progress=, new_progress)
end
def total=(new_total)
update_progress(:total=, new_total)
end
def progress_mark=(mark)
output.refresh_with_format_change { bar.progress_mark = mark }
end
def remainder_mark=(mark)
output.refresh_with_format_change { bar.remainder_mark = mark }
end
def title
title_comp.title
end
def title=(title)
output.refresh_with_format_change { title_comp.title = title }
end
def to_s(new_format = nil)
self.format = new_format if new_format
Format::Formatter.process(@format, output.length, self)
end
# rubocop:disable Metrics/AbcSize, Metrics/LineLength
def to_h
{
'output_stream' => output.__send__(:stream),
'length' => output.length,
'title' => title_comp.title,
'progress_mark' => bar.progress_mark,
'remainder_mark' => bar.remainder_mark,
'progress' => progressable.progress,
'total' => progressable.total,
'percentage' => progressable.percentage_completed_with_precision.to_f,
'elapsed_time_in_seconds' => time.__send__(:timer).elapsed_seconds,
'estimated_time_remaining_in_seconds' => time.__send__(:estimated_seconds_remaining),
'base_rate_of_change' => rate.__send__(:base_rate),
'scaled_rate_of_change' => rate.__send__(:scaled_rate),
'unknown_progress_animation_steps' => bar.upa_steps,
'throttle_rate' => output.__send__(:throttle).rate,
'started?' => started?,
'stopped?' => stopped?,
'finished?' => finished?
}
end
# rubocop:enable Metrics/AbcSize, Metrics/LineLength
def inspect
"#<ProgressBar:#{progress}/#{total || 'unknown'}>"
end
def format=(other)
output.refresh_with_format_change do
@format = Format::String.new(other || output.default_format)
end
end
alias format format=
protected
attr_accessor :output,
:timer,
:progressable,
:title_comp,
:bar,
:percentage,
:rate,
:time,
:autostart,
:autofinish,
:finished
def update_progress(*args)
output.with_refresh do
progressable.__send__(*args)
timer.stop if finished?
end
end
end
end
class ProgressBar
module Calculators
class Length
attr_reader :length_override
attr_accessor :current_length,
:output
def initialize(options = {})
self.length_override = options[:length]
self.output = options[:output]
self.current_length = nil
end
def length
current_length || reset_length
end
def length_changed?
previous_length = current_length
self.current_length = calculate_length
previous_length != current_length
end
def calculate_length
length_override || terminal_width || 80
end
def reset_length
self.current_length = calculate_length
end
def length_override=(other)
@length_override ||= ENV['RUBY_PROGRESS_BAR_LENGTH'] || other
@length_override = @length_override.to_i if @length_override
end
private
# This code was copied and modified from Rake, available under MIT-LICENSE
# Copyright (c) 2003, 2004 Jim Weirich
# rubocop:disable Style/RescueStandardError
def terminal_width
return 80 unless unix?
result = dynamic_width
(result < 20) ? 80 : result
rescue
80
end
# rubocop:enable Style/RescueStandardError
# rubocop:disable Lint/DuplicateMethods
begin
require 'io/console'
def dynamic_width
if output && output.tty? && output.respond_to?(:winsize)
dynamic_width_via_output_stream_object
elsif IO.console
dynamic_width_via_io_object
else
dynamic_width_via_system_calls
end
end
rescue LoadError
def dynamic_width
dynamic_width_via_system_calls
end
end
# rubocop:enable Lint/DuplicateMethods
def dynamic_width_via_output_stream_object
_rows, columns = output.winsize
columns
end
def dynamic_width_via_io_object
_rows, columns = IO.console.winsize
columns
end
def dynamic_width_via_system_calls
dynamic_width_stty.nonzero? || dynamic_width_tput
end
def dynamic_width_stty
`stty size 2>/dev/null`.split[1].to_i
end
def dynamic_width_tput
`tput cols 2>/dev/null`.to_i
end
def unix?
RUBY_PLATFORM =~ /(aix|darwin|linux|(net|free|open)bsd|cygwin|solaris|irix|hpux)/i
end
end
end
end
class ProgressBar
module Calculators
class RunningAverage
def self.calculate(current_average, new_value_to_average, smoothing_factor)
new_value_to_average * (1.0 - smoothing_factor) + current_average * smoothing_factor
end
end
end
end
require 'ruby-progressbar/components/bar'
require 'ruby-progressbar/components/percentage'
require 'ruby-progressbar/components/rate'
require 'ruby-progressbar/components/time'
require 'ruby-progressbar/components/title'
###
# UPA = Unknown Progress Animation
#
class ProgressBar
module Components
class Bar
DEFAULT_PROGRESS_MARK = '='.freeze
DEFAULT_REMAINDER_MARK = ' '.freeze
DEFAULT_UPA_STEPS = ['=---', '-=--', '--=-', '---='].freeze
attr_accessor :progress_mark,
:remainder_mark,
:length,
:progress,
:upa_steps
def initialize(options = {})
self.upa_steps = options[:unknown_progress_animation_steps] || DEFAULT_UPA_STEPS
self.progress_mark = options[:progress_mark] || DEFAULT_PROGRESS_MARK
self.remainder_mark = options[:remainder_mark] || DEFAULT_REMAINDER_MARK
self.progress = options[:progress]
self.length = options[:length]
end
def to_s(options = { :format => :standard })
if progress.unknown?
unknown_string
elsif options[:format] == :standard
"#{standard_complete_string}#{incomplete_string}"
elsif options[:format] == :integrated_percentage
"#{integrated_percentage_complete_string}#{incomplete_string}"
end
end
private
def integrated_percentage_complete_string
return standard_complete_string if completed_length < 5
" #{progress.percentage_completed} ".to_s.center(completed_length, progress_mark)
end
def standard_complete_string
progress_mark * completed_length
end
def incomplete_string
remainder_mark * (length - completed_length)
end
def bar(length)
self.length = length
standard_complete_string
end
def complete_bar(length)
self.length = length
to_s(:format => :standard)
end
def complete_bar_with_percentage(length)
self.length = length
to_s(:format => :integrated_percentage)
end
def unknown_string
unknown_frame_string = unknown_progress_frame * ((length / upa_steps.size) + 2)
unknown_frame_string[0, length]
end
def incomplete_space(length)
self.length = length
if progress.unknown?
unknown_string
else
incomplete_string
end
end
def bar_with_percentage(length)
self.length = length
integrated_percentage_complete_string
end
def completed_length
(length * progress.percentage_completed / 100).floor
end
def unknown_progress_frame
current_animation_step = progress.progress % upa_steps.size
upa_steps[current_animation_step]
end
end
end
end
class ProgressBar
module Components
class Percentage
attr_accessor :progress
def initialize(options = {})
self.progress = options[:progress]
end
private
def percentage
progress.percentage_completed
end
def justified_percentage
progress.percentage_completed.to_s.rjust(3)
end
def percentage_with_precision
progress.percentage_completed_with_precision
end
def justified_percentage_with_precision
progress.percentage_completed_with_precision.to_s.rjust(6)
end
end
end
end
class ProgressBar
module Components
class Rate
attr_accessor :rate_scale,
:started_at,
:stopped_at,
:timer,
:progress
def initialize(options = {})
self.rate_scale = options[:rate_scale] || lambda { |x| x }
self.started_at = nil
self.stopped_at = nil
self.timer = options[:timer]
self.progress = options[:progress]
end
private
def rate_of_change(format_string = '%i')
return 0 unless elapsed_seconds > 0
format_string % scaled_rate
end
def rate_of_change_with_precision
rate_of_change('%.2f')
end
def scaled_rate
rate_scale.call(base_rate)
end
def base_rate
progress.absolute / elapsed_seconds
end
def elapsed_seconds
timer.elapsed_whole_seconds.to_f
end
end
end
end
###
# OOB = 'Out of Bounds'
#
class ProgressBar
module Components
class Time
TIME_FORMAT = '%02d:%02d:%02d'.freeze
OOB_TIME_FORMATS = [:unknown, :friendly, nil].freeze
OOB_LIMIT_IN_HOURS = 99
OOB_UNKNOWN_TIME_TEXT = '??:??:??'.freeze
OOB_FRIENDLY_TIME_TEXT = '> 4 Days'.freeze
NO_TIME_ELAPSED_TEXT = '--:--:--'.freeze
ESTIMATED_LABEL = ' ETA'.freeze
ELAPSED_LABEL = 'Time'.freeze
def initialize(options = {})
self.out_of_bounds_time_format = options[:out_of_bounds_time_format]
self.timer = options[:timer]
self.progress = options[:progress]
end
def estimated_with_label
"#{ESTIMATED_LABEL}: #{estimated}"
end
def elapsed_with_label
"#{ELAPSED_LABEL}: #{elapsed}"
end
protected
def estimated_with_no_oob
self.out_of_bounds_time_format = nil
estimated_with_elapsed_fallback
end
def estimated_with_unknown_oob
self.out_of_bounds_time_format = :unknown
estimated_with_elapsed_fallback
end
def estimated_with_friendly_oob
self.out_of_bounds_time_format = :friendly
estimated_with_elapsed_fallback
end
attr_reader :out_of_bounds_time_format
attr_accessor :timer,
:progress
def out_of_bounds_time_format=(format)
unless OOB_TIME_FORMATS.include? format
fail 'Invalid Out Of Bounds time format. Valid formats are ' +
OOB_TIME_FORMATS.inspect
end
@out_of_bounds_time_format = format
end
private
def estimated
memo_estimated_seconds_remaining = estimated_seconds_remaining
return OOB_UNKNOWN_TIME_TEXT unless memo_estimated_seconds_remaining
hours, minutes, seconds = timer.divide_seconds(memo_estimated_seconds_remaining)
if hours > OOB_LIMIT_IN_HOURS && out_of_bounds_time_format
out_of_bounds_time
else
TIME_FORMAT % [hours, minutes, seconds]
end
end
def elapsed
return NO_TIME_ELAPSED_TEXT unless timer.started?
hours, minutes, seconds = timer.divide_seconds(timer.elapsed_whole_seconds)
TIME_FORMAT % [hours, minutes, seconds]
end
def estimated_with_elapsed_fallback
progress.finished? ? elapsed_with_label : estimated_with_label
end
def estimated_seconds_remaining
return if progress.unknown? || progress.none? || timer.stopped?
(timer.elapsed_seconds * (progress.total / progress.running_average - 1)).round
end
def out_of_bounds_time
case out_of_bounds_time_format
when :unknown
OOB_UNKNOWN_TIME_TEXT
when :friendly
OOB_FRIENDLY_TIME_TEXT
end
end
end
end
end
class ProgressBar
module Components
class Title
DEFAULT_TITLE = 'Progress'.freeze
attr_accessor :title
def initialize(options = {})
self.title = options[:title] || DEFAULT_TITLE
end
end
end
end
class ProgressBar
class InvalidProgressError < RuntimeError
end
end
require 'ruby-progressbar/format/molecule'
require 'ruby-progressbar/format/formatter'
require 'ruby-progressbar/format/string'
class ProgressBar
module Format
class Formatter
def self.process(format_string, max_length, bar)
processed_string = format_string.dup
format_string.non_bar_molecules.each do |molecule|
processed_string.gsub!(molecule.full_key, molecule.lookup_value(bar, nil))
end
processed_string.gsub!(/%%/, '%')
bar_length = max_length -
processed_string.displayable_length +
format_string.bar_molecule_placeholder_length
bar_length = (bar_length < 0) ? 0 : bar_length
format_string.bar_molecules.each do |molecule|
processed_string.gsub!(molecule.full_key,
molecule.lookup_value(bar, bar_length))
end
processed_string
end
end
end
end
class ProgressBar
module Format
class Molecule
MOLECULES = {
:t => [:title_comp, :title],
:T => [:title_comp, :title],
:c => [:progressable, :progress],
:C => [:progressable, :total],
:u => [:progressable, :total_with_unknown_indicator],
:p => [:percentage, :percentage],
:P => [:percentage, :percentage_with_precision],
:j => [:percentage, :justified_percentage],
:J => [:percentage, :justified_percentage_with_precision],
:a => [:time, :elapsed_with_label],
:e => [:time, :estimated_with_unknown_oob],
:E => [:time, :estimated_with_friendly_oob],
:f => [:time, :estimated_with_no_oob],
:B => [:bar, :complete_bar],
:b => [:bar, :bar],
:W => [:bar, :complete_bar_with_percentage],
:w => [:bar, :bar_with_percentage],
:i => [:bar, :incomplete_space],
:r => [:rate, :rate_of_change],
:R => [:rate, :rate_of_change_with_precision]
}.freeze
BAR_MOLECULES = %w{W w B b i}.freeze
attr_accessor :key,
:method_name
def initialize(letter)
self.key = letter
self.method_name = MOLECULES.fetch(key.to_sym)
end
def bar_molecule?
BAR_MOLECULES.include? key
end
def non_bar_molecule?
!bar_molecule?
end
def full_key
"%#{key}"
end
def lookup_value(environment, length = 0)
component = environment.__send__(method_name[0])
if bar_molecule?
component.__send__(method_name[1], length).to_s
else
component.__send__(method_name[1]).to_s
end
end
end
end
end
class ProgressBar
module Format
class String < ::String
MOLECULE_PATTERN = /%[a-zA-Z]/.freeze
ANSI_SGR_PATTERN = /\e\[[\d;]+m/.freeze
def displayable_length
gsub(ANSI_SGR_PATTERN, '').length
end
def bar_molecule_placeholder_length
@bar_molecule_placeholder_length ||= bar_molecules.size * 2
end
def non_bar_molecules
@non_bar_molecules ||= molecules.select(&:non_bar_molecule?)
end
def bar_molecules
@bar_molecules ||= molecules.select(&:bar_molecule?)
end
def molecules
@molecules ||= begin
molecules = []
scan(MOLECULE_PATTERN) do |match|
molecules << Molecule.new(match[1, 1])
end
molecules
end
end
end
end
end
class ProgressBar
class Output
DEFAULT_OUTPUT_STREAM = $stdout
attr_accessor :stream
def initialize(options = {})
self.bar = options[:bar]
self.stream = options[:output] || DEFAULT_OUTPUT_STREAM
self.throttle = Throttle.new(options)
self.length_calculator = Calculators::Length.new(
:length => options[:length],
:output => stream
)
end
def self.detect(options = {})
if options[:output].is_a?(Class) && options[:output] <= ProgressBar::Output
options[:output].new(options)
elsif (options[:output] || DEFAULT_OUTPUT_STREAM).tty?
Outputs::Tty.new(options)
else
Outputs::NonTty.new(options)
end
end
def log(string)
clear
stream.puts string
refresh(:force => true) unless bar.stopped?
end
def clear_string
' ' * length_calculator.length
end
def length
length_calculator.length
end
def with_refresh
yield
refresh
end
def refresh(options = {})
throttle.choke(:force_update_if => (bar.stopped? || options[:force])) do
clear if length_calculator.length_changed?
print_and_flush
end
end
protected
attr_accessor :length_calculator,
:throttle,
:bar
private
def print_and_flush
stream.print bar_update_string + eol
stream.flush
end
end
end
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment