class Fasten::UI::Curses

Constants

MOON_LEN
MOON_STR
PROGRESSBAR_LEN
PROGRESSBAR_STR
SPINNER_LEN
SPINNER_STR

Attributes

clear_needed[RW]
message[RW]
n_cols[RW]
n_rows[RW]
runner[RW]
sel_index[RW]
selected[RW]

Public Class Methods

new(runner:) click to toggle source
# File lib/fasten/ui/curses.rb, line 24
def initialize(runner:)
  @runner = runner
end

Public Instance Methods

cleanup() click to toggle source
# File lib/fasten/ui/curses.rb, line 46
def cleanup
  close_screen
  @setup_done = nil
end
draw_title() click to toggle source
# File lib/fasten/ui/curses.rb, line 40
def draw_title
  ui_text_aligned(0, :left, 'Fasten your seatbelts!')
  ui_text_aligned(0, :center, "#{name} #{$PID}")
  ui_text_aligned(0, :right, Time.new.to_s)
end
force_clear() click to toggle source
# File lib/fasten/ui/curses.rb, line 91
def force_clear
  self.clear_needed = true
end
setup() click to toggle source
# File lib/fasten/ui/curses.rb, line 51
def setup
  init_screen
  self.n_rows = lines
  self.n_cols = cols
  stdscr.keypad = true
  stdscr.nodelay = true
  setup_color
  noecho
  cbreak
  nonl
  curs_set 0
  @setup_done = true
end
setup_color() click to toggle source
# File lib/fasten/ui/curses.rb, line 65
def setup_color
  start_color
  use_default_colors

  init_pair 1, Curses::COLOR_YELLOW, -1
  init_pair 2, Curses::COLOR_GREEN, -1
  init_pair 3, Curses::COLOR_RED, -1
  init_pair 4, Curses::COLOR_WHITE, -1
end
ui_jobs() click to toggle source
# File lib/fasten/ui/curses.rb, line 138
def ui_jobs
  l = ui_text_aligned(1, :left, ui_jobs_summary) + 1

  workers.each_with_index do |worker, index|
    setpos 1, l + index
    attrs = worker.running? ? A_STANDOUT : color_pair(4) | A_DIM
    attrset attrs
    addstr worker.running? ? 'R' : '_'
    attroff attrs
  end

  ui_state
end
ui_jobs_summary() click to toggle source
# File lib/fasten/ui/curses.rb, line 129
def ui_jobs_summary
  running = tasks.running.count
  waiting = tasks.waiting.count
  working = workers.count
  idle    = working - running

  "Procs running: #{running} idle: #{idle} waiting: #{waiting} #{runner.use_threads ? 'threads' : 'processes'}: #{jobs}"
end
ui_keyboard() click to toggle source
# File lib/fasten/ui/curses.rb, line 95
def ui_keyboard
  return unless (key = stdscr.getch)

  self.message = nil

  if key == Curses::Key::LEFT
    if jobs <= 1
      self.message = "Can't remove 1 worker left, press [P] to pause"
    else
      self.jobs -= 1
      self.message = "Decreasing jobs to #{jobs}"
    end
  elsif key == Curses::Key::RIGHT
    self.jobs += 1
    self.message = "Increasing jobs to #{jobs}"
  elsif key == Curses::Key::DOWN
    self.sel_index = sel_index ? [sel_index + 1, tasks.count - 1].min : 0
    self.selected = tasks[sel_index]
  elsif key == Curses::Key::UP
    self.sel_index = sel_index ? [sel_index - 1, 0].max : tasks.count - 1
    self.selected = tasks[sel_index]
  elsif key == 'q'
    self.message = 'Will quit when running tasks end'
    self.state = :QUITTING
  elsif key == 'p'
    self.message = 'Will pause when running tasks end'
    self.state = :PAUSING
  elsif key == 'r'
    self.state = :RUNNING
  end

  force_clear
end
ui_progressbar(row, col_ini, col_fin, count, total) click to toggle source
# File lib/fasten/ui/curses.rb, line 170
def ui_progressbar(row, col_ini, col_fin, count, total)
  slice = total.to_f / (col_fin - col_ini + 1)
  col_ini.upto col_fin do |col|
    setpos row, col
    count -= slice
    if count >= 0
      addstr PROGRESSBAR_STR[-1]
    elsif count > -slice
      addstr PROGRESSBAR_STR[(count * PROGRESSBAR_LEN / slice) % PROGRESSBAR_LEN]
    else
      addstr '.'
    end
  end
end
ui_state() click to toggle source
# File lib/fasten/ui/curses.rb, line 152
def ui_state
  if runner.running?
    attrs = color_pair(2)
  elsif runner.pausing?
    attrs = color_pair(1) | A_BLINK | A_STANDOUT
  elsif runner.paused?
    attrs = color_pair(1) | A_STANDOUT
  elsif runner.quitting?
    attrs = color_pair(3) | A_BLINK | A_STANDOUT
  end

  l = ui_text_aligned(1, :right, state.to_s, attrs)
  return unless message

  setpos 1, n_cols - l - message.length - 1
  addstr message
end
ui_task_clock(task, cur, avg) click to toggle source
# File lib/fasten/ui/curses.rb, line 200
def ui_task_clock(task, cur, avg)
  return unless task.ini

  dif = cur - task.ini
  avg = avg.to_f
  if task.ini && avg.positive?
    percent = dif / avg
    index = (percent * MOON_LEN).to_i
    index = MOON_LEN - 1 if index > MOON_LEN - 1

    format '  %.2f s %s ', dif, MOON_STR[index]
  else
    format '  %.2f s   ', dif
  end
end
ui_task_color(task) click to toggle source
# File lib/fasten/ui/curses.rb, line 216
def ui_task_color(task)
  rev = task == selected ? A_REVERSE : 0

  case task.state
  when :RUNNING
    color_pair(1) | A_TOP | rev
  when :DONE
    color_pair(2) | A_TOP | rev
  when :FAIL
    color_pair(3) | A_TOP | rev
  when :WAIT
    A_TOP | rev
  else
    color_pair(4) | A_DIM | rev
  end
end
ui_task_icon(task) click to toggle source
# File lib/fasten/ui/curses.rb, line 185
def ui_task_icon(task)
  case task.state
  when :RUNNING
    SPINNER_STR[task.worker&.spinner]
  when :FAIL
    '✘'
  when :DONE
    '✔'
  when :WAIT
    '…'
  else
    ' '
  end
end
ui_task_string(task, y, x, icon: nil, str: nil) click to toggle source
# File lib/fasten/ui/curses.rb, line 233
def ui_task_string(task, y, x, icon: nil, str: nil)
  setpos y, x

  attrs = ui_task_color(task)
  icon = ui_task_icon(task) if icon

  str ||= icon ? "#{icon} #{task}" : task.to_s

  delta = x + str.length - n_cols
  str = str[0...-delta] if delta.positive?

  attrset attrs if attrs
  addstr str
  attroff attrs if attrs

  x + str.length
end
ui_tasks() click to toggle source
# File lib/fasten/ui/curses.rb, line 251
def ui_tasks
  workers.each do |worker|
    worker.spinner = (worker.spinner + 1) % SPINNER_LEN if worker.running?
  end

  cur = Time.new

  count_done = tasks.done.count
  count_total = tasks.count
  tl = count_total.to_s.length
  percentstr = count_total.positive? && " #{(count_done * 100 / count_total).to_i}%"
  elapsed_str = format ' %.2f s', (dif = cur - runner.ini) if runner.ini

  @stat_str ||= begin
    @runner_last_avg = runner.last_avg
    if runner.last_avg && runner.last_err
      format '≈ %.2f s ± %.2f', runner.last_avg, runner.last_err
    elsif runner.last_avg
      format '≈ %.2f s', runner.last_avg
    end
  end

  end_str = [elapsed_str, @stat_str].compact.join(' ')

  if @runner_last_avg
    a = dif
    b = @runner_last_avg
  else
    a = count_done
    b = count_total
  end

  col_ini = ui_text_aligned(2, :left, format("Tasks %#{tl}d/%d%s", count_done, count_total, percentstr)) + 1
  col_fin = n_cols - 1 - end_str.length
  ui_text_aligned(2, :right, end_str)

  ui_progressbar(2, col_ini, col_fin, a, b)

  max = 2
  list = tasks.sort_by.with_index { |x, index| [x.run_score, index] }
  list.each_with_index do |task, index|
    next if 3 + index >= n_rows

    x = ui_task_string(task, 3 + index, 2, icon: true)
    max = x if x > max
  end

  list.each_with_index do |task, index|
    next if 3 + index >= n_rows

    if task.depends && !task.depends.empty?
      x = max
      x = ui_task_string(task, 3 + index, x, str: ':') + 1
      task.depends.each do |dependant_task|
        x = ui_task_string(dependant_task, 3 + index, x) + 1
      end
    else
      x = max + 1
      last_avg = task.last_avg
      last_err = task.last_err
      if task.dif
        str = format '  %.2f s', task.dif
      elsif last_avg && last_err
        str = format '%s ≈ %.2f s ± %.2f %s', ui_task_clock(task, cur, last_avg), last_avg, last_err, task.worker&.name
      elsif last_avg
        str = format '%s ≈ %.2f s %s', ui_task_clock(task, cur, last_avg), last_avg, task.worker&.name
      else
        str = ui_task_clock(task, cur, 0)
      end
      ui_task_string(task, 3 + index, x, str: str) if str
    end
  end
end
ui_text_aligned(row, align, str, attrs = nil) click to toggle source
# File lib/fasten/ui/curses.rb, line 75
def ui_text_aligned(row, align, str, attrs = nil)
  if align == :center
    setpos row, (n_cols - str.length) / 2
  elsif align == :right
    setpos row, n_cols - str.length
  else
    setpos row, 0
  end

  attrset attrs if attrs
  addstr str
  attroff attrs if attrs

  str.length
end
update() click to toggle source
# File lib/fasten/ui/curses.rb, line 28
def update
  setup unless @setup_done
  ui_keyboard
  clear if clear_needed
  draw_title
  ui_jobs
  ui_tasks

  refresh
  self.clear_needed = false
end