Lua Notes

kal.nelua at tip
Login

File chrestomathy/kal/nelua-2/kal.nelua from the latest check-in


     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
   100
   101
   102
   103
   104
   105
   106
   107
   108
   109
   110
   111
   112
   113
   114
   115
   116
   117
   118
   119
   120
   121
   122
   123
   124
   125
   126
   127
   128
   129
   130
   131
   132
   133
   134
require 'filestream'
require 'vector'
require 'io'
local time = require 'kaltime'

local Eat = @record{
  kcal:     number,
  fat:      number,
  carb:     number,
  protein:  number,
  brand:    string,
  name:     string,
}

local Day = @record{
  date:   time.Time,
  goal:   number,
  burnt:  number,
  eats:   vector(Eat),
}

function Day:populated(): boolean
  return self.date ~= 0 and self.goal ~= 0
end

function Day:deficit(): number
  local eaten = 0
  for _, e in ipairs(self.eats) do
    eaten = eaten + e.kcal
  end
  return eaten - self.burnt
end

local function load_days(file: filestream): (vector(Day), string)
  local result: vector(Day)
  local day: Day
  local lineno = 0

  for line <close> in file:lines() do
    lineno = lineno + 1
    if line == '' then
      if day:populated() then result:push(day) end
      day = Day{}
      continue
    elseif line == 'd now' then
      day.date = time.Now()
      continue
    end
    ##[[local function try(pat)]] local ok, matches <close> = string.matchview(line, #[pat]#) ##[[end]]
    ##[[local function orfail(kind)]] if not ok then return {}, string.format('bad %s in line #%d: %s', #[kind]#, lineno, line) end ##[[end]]
    switch line[1] do
    case 'd'_u8 then
      ## try '^d %S+ (%d+ %S+ %d+)$' orfail 'date'
      local ok, epoch = time.Parse('%0d %b %Y', matches[1]) ## orfail 'date'
      day.date = epoch
    case 't'_u8 then
      ## try '^t %d+ / (%d+) %([-%d]+%) = %d+%%$' orfail 'calorie goal'
      day.goal = tonumber(matches[1])
    case 'b'_u8 then
      ## try '^b (%d+) = %d+%% %([-%d]+%)$' orfail 'calories burned'
      day.burnt = tonumber(matches[1])
    case 'T'_u8 then
      -- ignore
    case 'k'_u8 then
      ## try '^k +%d+%% +(%d+) +(%d+) +(%d+) +(%d+) +(%S+) "(.-)"$'
      if not ok then return {}, 'bad k line: ' .. line end
      day.eats:push({
        kcal    = tonumber(matches[1]),
        fat     = tonumber(matches[2]),
        carb    = tonumber(matches[3]),
        protein = tonumber(matches[4]),
        brand   = string.copy(matches[5]),
        name    = string.copy(matches[6]),
      })
    else
      return {}, 'bad line: ' .. line
    end
  end

  if day:populated() then result:push(day) end
  return result
end

local function dump_days(days: vector(Day)): string
  local buffer: [256]byte
  local tbuffer = (@string){data=&buffer[0], size=#buffer}
  local first = true
  for i, d in ipairs(days) do
    if first then first = false else print '' end
    local eaten   : number
    local left    = d.goal
    local fat     : number
    local carb    : number
    local protein : number
    for _, e in ipairs(d.eats) do
      eaten   = eaten   + e.kcal
      left    = left    - e.kcal
      fat     = fat     + e.fat
      carb    = carb    + e.carb
      protein = protein + e.protein
    end
    local daypct     = eaten / d.goal * 100
    local burnpct    = d.burnt / eaten * 100
    local proteinpct = protein / (fat + carb + protein) * 100
    if not time.Format(tbuffer, '%A, %d %b %Y', d.date) then return string.format('unable to format date: %lu', d.date) end

    io.writef('d %s\n', tbuffer)
    io.writef('t %.0f / %.0f (%.0f) = %.0f%%\n', eaten, d.goal, left, daypct)
    io.writef('b %.0f = %.0f%% (%.0f)\n', d.burnt, burnpct, d:deficit())
    io.writef('T %.0f:%.0f:%.0f = %.0f %.0f%%\n', fat, carb, protein, fat+carb+protein, proteinpct)

    for _, e in ipairs(d.eats) do
      local pct = e.kcal / d.goal * 100
      io.writef('k %3.0f%% %4.0f %3.0f %3.0f %3.0f %s "%s"\n', pct, e.kcal, e.fat, e.carb, e.protein, cstring(e.brand), cstring(e.name))
    end
  end
  io.flush()
  return ''
end

##[[local function ordie()]] if err ~= '' then io.stderr:print(err) return 1 end ##[[end]]
local diet <close>, err <close>, errcode = io.open 'diet'  ## ordie()
local days, err = load_days(diet)  ## ordie()
defer
  for _, d in ipairs(days) do
    for _, e in ipairs(d.eats) do
      e.brand:destroy()
      e.name:destroy()
    end
    d.eats:destroy()
  end
  days:destroy()
end
local err = dump_days(days)  ## ordie()