class Judges::Update
- License
- MIT
Copyright - Copyright © 2024-2025 Yegor Bugayenko
Author -
Yegor Bugayenko (yegor256@gmail.com)
are not supposed to instantiate it yourself.
This class is instantiated by thebin/judge
command line interface. You
Theupdate
command.
- Copyright © 2024-2025 Yegor Bugayenko
- MIT
def cycle(opts, judges, fb, options, start, errors)
-
(Factbase::Churn)
- How many modifications have been made
Parameters:
-
errors
(Array
) -- List of errors -
start
(Float
) -- When we started -
options
(Judges::Options
) -- The options -
fb
(Factbase
) -- The factbase -
judges
(Judges::Judges
) -- The judges -
opts
(Hash
) -- The command line options
def cycle(opts, judges, fb, options, start, errors) churn = Factbase::Churn.new global = {} used = 0 elapsed(@loog, level: Logger::INFO) do done = judges.each_with_index do |judge, i| if opts['fail-fast'] && !errors.empty? @loog.info("Not running #{judge.name.inspect} due to #{errors.count} errors above, in --fail-fast mode") next end next unless include?(opts, judge.name) @loog.info("\n👉 Running #{judge.name} (##{i}) at #{judge.dir.to_rel} (#{start.ago} already)...") used += 1 elapsed(@loog, level: Logger::INFO) do c = one_judge(opts, fb, judge, global, options, errors) churn += c throw :"👍 The '#{judge.name}' judge made zero changes to #{fb.size} facts" if c.zero? throw :"👍 The '#{judge.name}' judge #{c} out of #{fb.size} facts" end rescue StandardError, SyntaxError => e @loog.warn(Backtrace.new(e)) errors << e.message end throw :"👍 #{done} judge(s) processed" if errors.empty? throw :"❌ #{done} judge(s) processed with #{errors.size} errors" end if used.zero? raise 'No judges were used, while at least one expected to run' if opts['expect-judges'] @loog.info('No judges were used (looks like an error); not failing because of --no-expect-judges') end unless errors.empty? raise "Failed to update correctly (#{errors.size} errors)" unless opts['quiet'] @loog.info('Not failing because of the --quiet flag provided') end churn end
def include?(opts, name)
def include?(opts, name) judges = opts['judge'] || [] return true if judges.empty? judges.any?(name) end
def initialize(loog)
-
loog
(Loog
) -- Logging facility
def initialize(loog) @loog = loog @start = Time.now end
def one_judge(opts, fb, judge, global, options, errors)
-
(Factbase::Churn)
- How many modifications have been made
Parameters:
-
errors
(Array
) -- List of errors -
options
(Judges::Options
) -- The options -
global
(Hash
) -- Global options -
judge
(Judges::Judge
) -- The judge -
fb
(Factbase
) -- The factbase -
opts
(Hash
) -- The command line options
def one_judge(opts, fb, judge, global, options, errors) local = {} start = Time.now begin if opts['lifetime'] && Time.now - @start > opts['lifetime'] throw :"👎 The '#{judge.name}' judge skipped, no time left" end Timeout.timeout(opts['timeout']) do judge.run(fb, global, local, options) end rescue Timeout::Error, Timeout::ExitException => e errors << "Judge #{judge.name} stopped by timeout after #{start.ago}: #{e.message}" throw :"👎 The '#{judge.name}' judge timed out after #{start.ago}: #{e.message}" end end
def run(opts, args)
-
(RuntimeError)
- If not exactly two arguments provided or directory is missing
Parameters:
-
args
(Array
) -- List of command line arguments -
opts
(Hash
) -- Command line options (start with '--')
def run(opts, args) raise 'Exactly two arguments required' unless args.size == 2 dir = args[0] raise "The directory is absent: #{dir.to_rel}" unless File.exist?(dir) start = Time.now impex = Judges::Impex.new(@loog, args[1]) fb = impex.import(strict: false) fb = Factbase::Logged.new(fb, @loog) if opts['log'] options = Judges::Options.new(opts['option']) if opts['options-file'] options += Judges::Options.new( File.readlines(opts['options-file']) .compact .reject(&:empty?) .map { |ln| ln.strip.split('=', 1).map(&:strip).join('=') } ) @loog.debug("Options loaded from #{opts['options-file']}") end if options.empty? @loog.debug('No options provided by the --option flag') else @loog.debug("The following options provided:\n\t#{options.to_s.gsub("\n", "\n\t")}") end judges = Judges::Judges.new(dir, opts['lib'], @loog, start:, shuffle: opts['shuffle'], boost: opts['boost'], demote: opts['demote']) c = 0 churn = Factbase::Churn.new errors = [] sum = fb.query('(eq what "judges-summary")').each.to_a if sum.empty? @loog.info('Summary fact not found') unless opts['summary'] == 'off' else @loog.info("Summary fact found:\n\t#{Factbase::FactAsYaml.new(sum.first).to_s.gsub("\n", "\n\t")}") end if !sum.empty? && opts['summary'] == 'add' && fb.query('(eq what "judges-summary")').delete! @loog.info('Summary fact deleted') end elapsed(@loog, level: Logger::INFO) do loop do c += 1 if c > 1 if opts['lifetime'] && Time.now - @start > opts['lifetime'] @loog.info("Not starting cycle ##{c}, no time left") c -= 1 break end @loog.info("\nStarting cycle ##{c}#{" (out of #{opts['max-cycles']})" if opts['max-cycles']}...") end delta = cycle(opts, judges, fb, options, start, errors) churn += delta impex.export(fb) if delta.zero? @loog.info("The update cycle ##{c} has made no changes to the factbase, let's stop") break end if !opts['max-cycles'].nil? && c >= opts['max-cycles'] @loog.info("Too many cycles already, as set by --max-cycles=#{opts['max-cycles']}, breaking") break end if opts['fail-fast'] && !errors.empty? @loog.info("Due to #{errors.count} errors we must stop at the update cycle ##{c}") break end @loog.info("The cycle #{c} did #{delta}") end throw :"👍 Update completed in #{c} cycle(s), did #{churn}" end return unless %w[add append].include?(opts['summary']) summarize(fb, churn, errors, start, c) impex.export(fb) end
def summarize(fb, churn, errors, start, cycles)
-
cycles
(Integer
) -- How many cycles -
start
(Time
) -- When we started -
errors
(Array
) -- List of errors -
churn
(Churn
) -- The churn -
fb
(Factbase
) -- The factbase
def summarize(fb, churn, errors, start, cycles) before = fb.query('(eq what "judges-summary")').each.to_a if before.empty? s = fb.insert s.what = 'judges-summary' s.when = Time.now s.version = Judges::VERSION s.seconds = Time.now - start s.cycles = cycles s.inserted = churn.inserted.size s.deleted = churn.deleted.size s.added = churn.added.size else s = before.first errs = s['errors']&.size || 0 @loog.info( "A summary found, with #{errs.positive? || 'no'} error#{'s' if errs > 1 || errs.zero?}: " \ "#{%w[when cycles version inserted deleted added].map { |a| "#{a}=#{s[a]&.first}" }.join(', ')}" ) end if errors.empty? @loog.info('No errors added to the summary') else errors.each { |e| s.error = e } @loog.info("#{errors.size} error#{'s' if errors.size > 1} added to the summary") end end