bio-miga/miga

View on GitHub
lib/miga/common.rb

Summary

Maintainability
B
4 hrs
Test Coverage
A
95%
# frozen_string_literal: true

require 'zlib'
require 'stringio'
require 'miga/version'
require 'miga/json'
require 'miga/parallel'
require 'miga/common/base'
require 'miga/common/errors'
require 'miga/common/path'
require 'miga/common/format'
require 'miga/common/net'
require 'miga/common/system_call'

##
# Generic class used to handle system-wide information and methods, and parent
# of all other MiGA::* classes.
class MiGA::MiGA
  include MiGA::Common

  extend MiGA::Common::Path
  extend MiGA::Common::Format
  extend MiGA::Common::Net
  extend MiGA::Common::SystemCall

  ENV['MIGA_HOME'] ||= ENV['HOME']

  ##
  # Path to the +.miga_rc+ file
  def self.rc_path
    File.join(ENV['MIGA_HOME'], '.miga_rc')
  end

  ##
  # Has MiGA been initialized?
  def self.initialized?
    File.exist?(rc_path) &&
      File.exist?(File.join(ENV['MIGA_HOME'], '.miga_daemon.json'))
  end

  ##
  # Check if the result files exist with +base+ name (String) followed by the
  # +ext+ values (Array of String).
  def result_files_exist?(base, ext)
    ext = [ext] unless ext.is_a? Array
    MiGA::MiGA.DEBUG("Assserting files for result: #{ext}")
    ext.all? { |f| File.exist?(base + f) or File.exist?("#{base}#{f}.gz") }
  end

  ##
  # Print +par+ ensuring new line at the end.
  # Date/time-stamp each line.
  # If the first parameter is +IO+ or +StringIO+ the output is sent there,
  # otherwise it's sent to +$stderr+
  def say(*par)
    io = like_io?(par.first) ? par.shift : $stderr
    io.puts(*par.map { |i| "[#{Time.now}] #{i}" })
  end

  ##
  # Reports the advance of a task at +step+ (String), the +n+ out of +total+.
  # The advance is reported in powers of 1,024 if +bin+ is true, or powers of
  # 1,000 otherwise.
  # The report goes to $stderr iff --verbose
  def advance(step, n = 0, total = nil, bin = true)
    # Initialize advance timing
    @_advance_time ||= { last: nil, n: 0, avg: nil }
    if @_advance_time[:n] > n
      @_advance_time[:last] = nil
      @_advance_time[:n] = 0
      @_advance_time[:avg]  = nil
    end

    # Estimate timing
    adv_n = n - @_advance_time[:n]
    if total.nil? || @_advance_time[:last].nil? || adv_n.negative?
      @_advance_time[:last] = Time.now
      @_advance_time[:n] = n
    elsif adv_n > 0.001 * total
      this_time = (Time.now - @_advance_time[:last]).to_f
      this_avg = this_time / adv_n
      @_advance_time[:avg] ||= this_avg
      @_advance_time[:avg] = 0.9 * @_advance_time[:avg] + 0.1 * this_avg
      @_advance_time[:last] = Time.now
      @_advance_time[:n] = n
    end

    # Report
    adv =
      if total.nil?
        (n == 0 ? '' : num_suffix(n, bin))
      else
        vals = [100.0 * n / total, num_suffix(n, bin), num_suffix(total, bin)]
        ('%.1f%% (%s/%s)' % vals)
      end
    left =
      if @_advance_time[:avg].nil?
        ''
      else
        left_time = @_advance_time[:avg] * (total - n) / 60 # <- in minutes
        left_time < 0.01 ? '         ' :
          left_time < 1 ? ('%.0fs left' % (left_time * 60)) :
          left_time > 1440 ? ('%.1fd left' % (left_time / 1440)) :
          left_time > 60 ? ('%.1fh left' % (left_time / 60)) :
          ('%.1fm left' % left_time)
      end
    $stderr.print("[%s] %s %s %s    \r" % [Time.now, step, adv, left])
  end

  ##
  # Return formatted number +n+ with the appropriate units as
  # powers of 1,000 (if +bin+ if false) or 1,024 (otherwise)
  def num_suffix(n, bin = false)
    p = ''
    { T: 4, G: 3, M: 2, K: 1 }.each do |k, x|
      v = (bin ? 1024 : 1e3)**x
      if n > v
        n = '%.1f' % (n.to_f / v)
        p = k
        break
      end
    end
    "#{n}#{p}"
  end

  def like_io?(obj)
    obj.is_a?(IO) || obj.is_a?(StringIO)
  end
end