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 the bin/judge command line interface. You
The update command.

def cycle(opts, judges, fb, options, start, errors)

Returns:
  • (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)

Parameters:
  • loog (Loog) -- Logging facility
def initialize(loog)
  @loog = loog
  @start = Time.now
end

def one_judge(opts, fb, judge, global, options, errors)

Returns:
  • (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)

Raises:
  • (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)

Parameters:
  • 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