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()
|