class LucaTerm::Book

Attributes

window[RW]

Public Class Methods

journals(window, *args) click to toggle source
# File lib/luca_term/book.rb, line 25
def self.journals(window, *args)
  new(window, args[0], args[1], LucaSupport::Code.readable(LucaBook::List.term(*args).data))
end
new(window, year, month, data=nil) click to toggle source
# File lib/luca_term/book.rb, line 13
def initialize(window, year, month, data=nil)
  @window = window
  @year = year
  @month = month
  @data = data
  @index = 0
  @active = 0  # active line in window
  @visible = set_visible(@data)
  @dict = LucaRecord::Dict.load('base.tsv')
  main_loop
end

Public Instance Methods

edit_amount(current = nil) click to toggle source
# File lib/luca_term/book.rb, line 201
def edit_amount(current = nil)
  begin
    scmd = edit_dialog "Current: #{current&.to_s}"
    return nil if scmd.length == 0
    # TODO: guard from not number
    return scmd.to_i
  rescue
    return nil
  end
end
edit_dialog(message = '') click to toggle source
# File lib/luca_term/book.rb, line 212
def edit_dialog(message = '')
  sub = window.subwin(4, 30, (window.maxy-4)/2, (window.maxx - 30)/2)
  sub.box(?|, ?-)
  sub.setpos(1, 1)
  padding = [0, 30 - message.length - 4].max
  sub << "  #{message + ' ' * padding}"
  clrtoeol
  sub.setpos(2, 1)
  sub << "  > #{' ' * (30 - 6)}"
  sub.setpos(2, 6)
  clrtoeol
  sub.refresh
  loop do
    echo
    scmd = sub.getstr
    noecho
    sub.close
    return scmd
  end
end
main_loop() click to toggle source
# File lib/luca_term/book.rb, line 29
def main_loop
  loop do
    window.setpos(0,0)
    @visible.each.with_index(0) do |dat, i|
      cursor = i == @active ? :full : nil
      draw_line(dat, cursor, true)
      clrtoeol
      window << "\n"
    end
    (window.maxy - window.cury).times { window.deleteln() }
    window.refresh

    window.keypad(true)
    cmd = window.getch
    case cmd
    when KEY_DOWN, 'j', KEY_CTRL_N
      next if @index >= @data.length - 1

      cursor_down @data
    when KEY_UP, 'k', KEY_CTRL_P
      next if @index <= 0

      cursor_up @data
    when 'G'
      cursor_last @data
    when 'm'
      ym = edit_dialog('Change month: [yyyy] m')&.split(/[\/\s]/)
      ym = [@year, ym[0]] if ym.length == 1
      @data = LucaSupport::Code.readable(LucaBook::List.term(*ym).data)
      @year, @month = ym
      @index = 0
      @active = 0
      @visible = set_visible(@data)
    when '<'
      target = Date.parse("#{@year}-#{@month}-1").prev_month
      @data = LucaSupport::Code.readable(LucaBook::List.term(target.year, target.month).data)
      @year, @month = target.year, target.month
      @index = 0
      @active = 0
      @visible = set_visible(@data)
    when '>'
      target = Date.parse("#{@year}-#{@month}-1").next_month
      @data = LucaSupport::Code.readable(LucaBook::List.term(target.year, target.month).data)
      @year, @month = target.year, target.month
      @index = 0
      @active = 0
      @visible = set_visible(@data)
    when KEY_ENTER, KEY_CTRL_J
      show_detail(@data[@index])
    when 'N'
      newdate = edit_dialog "Enter date of new record: YYYY-m-d"
      tmpl = {
        date: newdate,
        debit: [
          { code: '10XX', amount: 0 }
        ],
        credit: [
          { code: '50XX', amount: 0 }
        ],
      }
      show_detail(tmpl)
    when 'q'
      exit 0
    end
  end
end
select_code() click to toggle source
# File lib/luca_term/book.rb, line 233
def select_code
  list = @dict.map{ |code, entry| { code: code, label: entry[:label] } }
           .select{ |d| d[:code].length >= 3 }
  visible_dup = @visible
  index_dup = @index
  active_dup = @active
  @index = 0
  @active = 0
  @visible = nil
  @visible = set_visible(list)
  loop do
    window.setpos(0,0)
    @visible.each.with_index(0) do |entry, i|
      line = format("%s %s", entry[:code], entry[:label])
      if i == @active
        window.attron(A_REVERSE) { window << line }
      else
        window << line
      end
      clrtoeol
      window << "\n"
    end
    (window.maxy - window.cury).times { window.deleteln() }
    window.refresh

    cmd = window.getch
    case cmd
    when KEY_DOWN, 'j', KEY_CTRL_N
      next if @index >= list.length - 1

      cursor_down list
    when KEY_NPAGE
      cursor_pagedown list
    when KEY_UP, 'k', KEY_CTRL_P
      next if @index <= 0

      cursor_up list
    when KEY_PPAGE
      cursor_pageup list
    when 'G'
      cursor_last list
    when KEY_CTRL_J
      code = list[@index][:code]
      @visible = visible_dup
      @index = index_dup
      @active = active_dup
      return code
    when 'q'
      @visible = visible_dup
      @index = index_dup
      @active = active_dup
      return nil
    end
  end
end
show_detail(record) click to toggle source
# File lib/luca_term/book.rb, line 96
def show_detail(record)
  @d_v = 0
  @d_h = 0
  debit_length = Array(record[:debit]).length
  credit_length = Array(record[:credit]).length
  date, txid = LucaSupport::Code.decode_id(record[:id]) if record[:id]
  date ||= record[:date]
  loop do
    window.setpos(0, 0)
    window << "#{date}  "
    window << record[:note]
    clrtoeol; window << "\n"
    [debit_length, credit_length].max.times do |i|
      { id: nil, debit: [], credit: [] }.tap do |dat|
        if i < debit_length
          dat[:debit] << Array(record[:debit])[i]
        end
        if i < credit_length
          dat[:credit] << Array(record[:credit])[i]
        end
        cursor = @d_v == i ? @d_h : nil
        draw_line(dat, cursor)
      end
      clrtoeol
      window << "\n"
    end
    (window.maxy - window.cury).times { window.deleteln() }
    window.refresh

    window.keypad(true)
    cmd = window.getch
    case cmd
    when KEY_DOWN, 'j', KEY_CTRL_N
      case @d_h
      when 0, 1
        @d_v = @d_v >= debit_length - 1 ? 0 : @d_v + 1
      else  #2,3
        @d_v = @d_v >= credit_length - 1 ? 0 : @d_v + 1
      end
    when KEY_UP, 'k', KEY_CTRL_P
      case @d_h
      when 0, 1
        @d_v = @d_v == 0 ? debit_length - 1 : @d_v - 1
      else  #2,3
        @d_v = @d_v == 0 ? credit_length - 1 : @d_v - 1
      end
    when KEY_LEFT, 'h', KEY_CTRL_B
      case @d_h
      when 1, 3
        @d_h -= 1
      when 2
        @d_v = debit_length - 1 if @d_v > debit_length - 1
        @d_h -= 1
      else # 0
        @d_v = credit_length - 1 if @d_v > credit_length - 1
        @d_h = 3
      end
    when KEY_RIGHT, 'l', KEY_CTRL_F
      case @d_h
      when 0, 2
        @d_h += 1
      when 1
        @d_v = credit_length - 1 if @d_v > credit_length - 1
        @d_h += 1
      else # 3
        @d_v = debit_length - 1 if @d_v > debit_length - 1
        @d_h = 0
      end
    when 'n'
      position = [0, 1].include?(@d_h) ? :debit : :credit
      new_code = select_code
      next if new_code.nil?

      new_amount = edit_amount
      next if new_amount.nil?

      record[position] << { code: new_code, amount: new_amount }
      debit_length = Array(record[:debit]).length
      credit_length = Array(record[:credit]).length
    when KEY_CTRL_J
      position = [0,1].include?(@d_h) ? :debit : :credit
      if [0, 2].include? @d_h
        new_code = select_code
        next if new_code.nil?

        record[position][@d_v][:code] = new_code
      else
        new_amount = edit_amount(record[position][@d_v][:amount])
        next if new_amount.nil?

        record[position][@d_v][:amount] = new_amount
      end
    when 's', KEY_CTRL_S
      if record[:id]
        LucaBook::Journal.save record
      else
        LucaBook::Journal.create record
      end
      break
    when 'q'
      break
    end
  end
end

Private Instance Methods

cursor_down(data) click to toggle source
# File lib/luca_term/book.rb, line 366
def cursor_down(data)
  @index += 1
  @active = @active >= window.maxy - 1 ? window.maxy - 1 : @active + 1
  @visible = set_visible(data)
end
cursor_last(data) click to toggle source
# File lib/luca_term/book.rb, line 381
def cursor_last(data)
  @index = data.length - 1
  @active = window.maxy - 1
  @visible = set_visible(data)
end
cursor_pagedown(data) click to toggle source
# File lib/luca_term/book.rb, line 372
def cursor_pagedown(data)
  n_idx = @index + window.maxy
  return if n_idx >= data.length - 1

  @index = n_idx
  @active = 0
  @visible = set_visible(data)
end
cursor_pageup(data) click to toggle source
# File lib/luca_term/book.rb, line 357
def cursor_pageup(data)
  n_idx = @index - window.maxy
  return if n_idx <= 0

  @index = n_idx
  @active = 0
  @visible = set_visible(data)
end
cursor_up(data) click to toggle source
# File lib/luca_term/book.rb, line 351
def cursor_up(data)
  @index -= 1
  @active = @active <= 0 ? 0 : @active - 1
  @visible = set_visible(data)
end
draw_line(dat, cursor = nil, note = false) click to toggle source
# File lib/luca_term/book.rb, line 291
def draw_line(dat, cursor = nil, note = false)
  date, txid = LucaSupport::Code.decode_id(dat[:id]) if dat[:id]
  debit_cd = fmt_code(dat[:debit])
  debit_amount = fmt_amount(dat[:debit])
  credit_cd = fmt_code(dat[:credit])
  credit_amount = fmt_amount(dat[:credit])
  lines = [Array(dat[:debit]).length, Array(dat[:credit]).length].max
  window << sprintf("%s %s |%s| ",
                    date&.mb_rjust(10, ' ') || '',
                    txid || '',
                    lines > 1 ? lines.to_s : ' ',
                   )
  case cursor
  when 0
    window.attron(A_REVERSE) { window << debit_cd }
    window << sprintf(" %s | %s %s", debit_amount, credit_cd, credit_amount)
  when 1
    window << sprintf("%s ", debit_cd)
    window.attron(A_REVERSE) { window << debit_amount }
    window << sprintf(" | %s %s", credit_cd, credit_amount)
  when 2
    window << sprintf("%s %s | ", debit_cd, debit_amount)
    window.attron(A_REVERSE) { window << credit_cd }
    window << sprintf(" %s", credit_amount)
  when 3
    window << sprintf("%s %s | %s ", debit_cd, debit_amount, credit_cd)
    window.attron(A_REVERSE) { window << credit_amount }
  else
    rest = format("%s %s | %s %s", debit_cd, debit_amount, credit_cd, credit_amount)
    if note && window.maxx > 80
      rest += " | #{dat[:note].mb_truncate(window.maxx - 80)}"
    end
    if cursor == :full
      window.attron(A_REVERSE) { window << rest }
    else
      window << rest
    end
  end
end
fmt_amount(record) click to toggle source
# File lib/luca_term/book.rb, line 346
def fmt_amount(record)
  amount = Array(record).dig(0, :amount) || ''
  amount.to_s.mb_rjust(10, ' ')
end
fmt_code(record) click to toggle source
# File lib/luca_term/book.rb, line 331
def fmt_code(record)
  cd = Array(record).dig(0, :code)
  width = (window.maxx / 3) < 30 ? 12 : 17
  return ''.mb_ljust(width, ' ') if cd.nil?

  label = @dict.dig(cd, :label)&.mb_truncate(12, omission: '')
  if width == 12
    label&.mb_ljust(width, ' ') || ''
  else
    sprintf("%s %s",
            cd&.mb_ljust(4, ' ') || '',
            label&.mb_ljust(12, ' ') || '')
  end
end
set_visible(data) click to toggle source
# File lib/luca_term/book.rb, line 387
def set_visible(data)
  return data if data.nil? || data.length <= window.maxy

  if @visible.nil?
    data.slice(0, window.maxy)
  else
    if @active == (window.maxy - 1)
      data.slice(@index - window.maxy + 1, window.maxy)
    elsif @active == 0
      data.slice(@index, window.maxy)
    else
      @visible
    end
  end
end