Compare commits

...

2 Commits

Author SHA1 Message Date
olari
6e07d120a2 update 2021-06-01 11:38:57 +03:00
olari
a10d3e1326 update 2021-05-31 12:47:57 +03:00
5 changed files with 234 additions and 189 deletions

2
.gitignore vendored
View File

@@ -1,3 +1,5 @@
**/*.csv
.ipynb_checkpoints/
__pycache__/
diet
journal.json

82
foods
View File

@@ -1,4 +1,62 @@
Kaurakeksi
Energy 461kcal
Fat 20g
SaturatedFat 8.5g
Carbs 56g
Sugar 31g
Fiber 4.8g
Protein 7.9g
Salt 0.2g
Cereal
Energy 373kcal
Fat 1.4g
SaturatedFat 0.4g
Carbs 78g
Sugar 13.9g
Fiber 7.1g
Protein 8.5g
Salt 0.39g
SadonkorjuuPuuro
Energy 385kcal
Fat 11g
SaturatedFat 1.7g
Carbs 51g
Sugar 1.1g
Fiber 11g
Protein 14g
DaimCake
Energy 446kcal
Fat 28g
SaturatedFat 9.3g
Carbs 39g
Sugar 37g
Protein 9.1g
Salt 0.35g
KaneliKierre
Energy 350kcal
Fat 14g
SaturatedFat 5.6g
Carbs 55g
Sugar 20g
Fiber 2g
Protein 6g
Salt 0.7g
Munkki
Energy 395kcal
Fat 23g
SaturatedFat 10g
Carbs 39g
Sugar 9.6g
Fiber 2.7g
Protein 5.8g
Salt 0.5g
ProteinRakha
Energy 67kcal
Fat 0.2g
@@ -693,6 +751,10 @@ Milk 180g
Potato 1000g
TOTAL 1300g
MashedPotato3
Potato 1050g
Milk 350g
Blov
BrownRice 600g
Ketchup 250g
@@ -791,6 +853,12 @@ Milk 240g
Egg 65g
GrahamFlour 60g
VegeKotlet2
HarkisRouheseos 200g
Milk 450g
WholeGrainBread 50g
Flour 90g
RoastedVeggies2
Broccoli 300g
Oil 70g
@@ -804,9 +872,23 @@ PeasCornPepper 200g
Ketchup 100g
TOTAL 760g
BeanitHarkisVeggies2
Oil 40g
BeanitHarkis 250g
PeasCornPepper 220g
Ketchup 100g
TOTAL 740g
Pancake
SkimMilk 430g
Water 300g
Oil 30g
Flour 430g
Sugar 20g
SadonkorjuuPuuroBoiled
SadonkorjuuPuuro 160g
SkimMilk 100g
Margarine 20g
TOTAL 788

224
parse.py
View File

@@ -1,110 +1,162 @@
from datetime import datetime, timedelta
from pathlib import Path
from subprocess import run
from typing import Union
import sys
from datetime import datetime
import re
import json
from functools import reduce
JOURNAL_PATH = '/Users/olari/workspace/journal'
entry_re = re.compile(r'^(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})', re.MULTILINE)
entry_re = re.compile(r'^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}')
curr_day = ''
def get_daily_file_paths() -> list[Path]:
return list(sorted(Path(JOURNAL_PATH).glob('*.md')))
def resolve_relative_time_expression(now: datetime, expr: str) -> datetime:
weekdays = [
'monday', 'tuesday', 'wednesday', 'thursday',
'friday', 'saturday', 'sunday'
]
def parse_godword(godword):
return godword.split()
if expr == 'today':
return now
elif expr == 'tomorrow':
return now + timedelta(days=1)
elif expr == 'yesterday':
return now - timedelta(days=1)
elif expr in weekdays:
return now - timedelta(days=now.weekday()) + weekdays.index(expr)
else:
return None
def parse_godword(content: str) -> list[str]:
return content.split()
def parse_habits(content: str) -> dict[str, bool]:
return {
line[4:]: line[1] == 'x'
for line in content.splitlines()
}
def parse_habits(habits):
result = {}
for habit in habits.splitlines():
value, name = habit.split(maxsplit=1)
name = name.strip()
result[name] = value[1] == 'x'
return result
header_modules = {
'godword': (None, parse_godword),
'habits': (None, parse_habits),
'godword': parse_godword,
'habits': parse_habits,
}
def parse_header_module(content: str) -> tuple[str, Union[list, dict]]:
name, *content = content.splitlines()
name = name.removesuffix(':').lower()
content = '\n'.join(content)
_, parse = header_modules[name]
return name, parse(content)
def parse_header(header):
title, *modules = header.split('\n\n')
title = title.removeprefix('# ')
return {
'title': title,
'modules': dict(parse_header_module(module) for module in modules)
}
result = {}
def parse_diet(content):
pass
def split_into_blocks(text):
return [b.strip() for b in re.split(r'\n{2,}', text) if b.strip() != '']
def parse_content(content):
content = content.strip()
return {
'blocks': [b.replace('\n', ' ') for b in content.split('\n\n')]
}
title, *modules = split_into_blocks(header)
for module in modules:
name, value = module.split('\n', maxsplit=1)
name = name.lower().removesuffix(':')
result[name] = header_modules[name](value)
return result
def parse_timestamp(timestamp):
return datetime.strptime(timestamp, '%Y-%m-%d %H:%M:%S')
def parse_post(block):
block = block.removeprefix('@post ')
try:
timestamp = int(parse_timestamp(block[:19]).timestamp())
block = block[19:]
except:
timestamp = None
content = block
return {'timestamp': timestamp, 'content': content}
def parse_notes(block):
tag, source, title = block.splitlines()
return {'source': source, 'title': title}
def parse_diet(block):
tag, amount, food = block.split()
amount = float(amount.removesuffix('g'))
return {'amount': amount, 'food': food}
def parse_timer(block):
tag, *rest = block.split()
name = None
timestamp = None
if len(rest) > 2:
name, *rest = rest
if len(rest) > 1:
timestamp = int(parse_timestamp(' '.join(rest)).timestamp())
result = {}
if name:
result['name'] = name
if timestamp:
result['timestamp'] = timestamp
return result
def parse_exercise(block):
tag, *parts = block.split()
if parts[0] == 'walk':
kind, minutes, distance, steps = parts
return {
'kind': kind,
'minutes': int(minutes.removesuffix('min')),
'distance': float(distance.removesuffix('km')),
'steps': int(steps.removesuffix('steps')),
}
return {'kind': 'INVALID'}
def create_entry_module_parser(name, handler=None):
handler = handler or (lambda b: {'value': b.removeprefix(f'@{name} ')})
return lambda b: {'type': name} | handler(b)
entry_modules = {
'hide': create_entry_module_parser('hide', lambda _: {}),
'post': create_entry_module_parser('post', parse_post),
'info': create_entry_module_parser('info'),
'notes': create_entry_module_parser('notes', parse_notes),
'behavior': create_entry_module_parser('behavior'),
'diet': create_entry_module_parser('diet', parse_diet),
'task': create_entry_module_parser('task'),
'start': create_entry_module_parser('start', parse_timer),
'stop': create_entry_module_parser('stop', parse_timer),
'done': create_entry_module_parser('done', parse_timer),
'exercise': create_entry_module_parser('exercise', parse_exercise),
}
def parse_entry(entry):
return {
'timestamp': int(parse_timestamp(entry[:19]).timestamp()),
'content': parse_content(entry[19:]),
}
def parse_file(fpath):
header = {}
entries = []
buf = []
for i, line in enumerate(fpath.read_text().splitlines()):
if entry_re.match(line):
if not header:
header = parse_header('\n'.join([c.strip() for c in buf]))
else:
entries.append(parse_entry('\n'.join(buf)))
buf = [line]
else:
buf.append(line)
return {
'header': header,
'entries': entries,
}
def parse_journal():
result = {}
for fpath in get_daily_file_paths()[-5:]:
info = parse_file(fpath)
result[info['header']['title']] = info
def split_into_blocks(text):
result = []
for block in re.split(r'\n{2,}', text):
block = block.strip()
if not block:
continue
for i, module in enumerate(block.replace(' @', '\n@').split('\n@')):
#module = module.strip().replace('\n', ' ')
if i == 0:
result.append(module)
else:
result.append('@'+module)
return result
timestamp, content = entry
result['timestamp'] = int(parse_timestamp(timestamp.strip()).timestamp())
result['blocks'] = []
for b in split_into_blocks(content):
if b[0] == '@':
tag = b.split()[0][1:]
result['blocks'].append(entry_modules[tag](b))
else:
result['blocks'].append(b)
return result
import json
open('journal.json', 'w').write(json.dumps(parse_journal()))
result = {}
for fpath in list(sorted((Path.home() / 'workspace' / 'journal').glob('*.md'))):
curr_day = fpath.stem
header, *tmp = entry_re.split(fpath.read_text())
entries = list(zip(tmp[::2], tmp[1::2]))
result[fpath.stem] = {
'header': parse_header(header),
'entries': [parse_entry(e) for e in entries],
}
with open('journal.json', 'w') as fp:
json.dump(result, fp, indent=4, ensure_ascii=False)

View File

@@ -7,27 +7,11 @@ from common import parse_timestamp
content = open(sys.argv[1]).read().strip()
"""
* Total hours
* Hours today
* Hours this week
* Percentage completed
* Pages completed
* Estimated hours to completion
* Estimated completion date
"""
lines = content.splitlines()
i = 0
current_chapter = ''
result = defaultdict(float)
total_chapters = 0
completed_chapters = 0
completed_chapters = set()
today = datetime.now().replace(hour=0,minute=0,second=0,microsecond=0)
this_week = today - timedelta(days=7)
@@ -38,13 +22,17 @@ week_hours = 0.0
oldest_timestamp = datetime.now()
i = 0
while i < len(lines):
line = lines[i].strip()
if line.startswith('#'):
current_chapter = line[line.find(' ')+1:]
total_chapters += 1
elif line.startswith('@start'):
else:
completed_chapters.add(current_chapter)
if line.startswith('@start'):
start = parse_timestamp(line.removeprefix('@start '))
if start < oldest_timestamp:
@@ -54,28 +42,18 @@ while i < len(lines):
line = lines[i].strip()
end = parse_timestamp(line.removeprefix('@stop '))
delta = end - start
hours = delta.seconds / 60 / 60
result[current_chapter] += hours
total_hours += hours
if start > this_week:
week_hours += hours
if start > today:
day_hours += hours
elif line.startswith('@done'):
completed_chapters += 1
i += 1
#from pprint import pprint
#pprint(dict(result), sort_dicts=False)
completed_chapters = len(completed_chapters)
num_days = (datetime.now() - oldest_timestamp).days or 1
hours_per_day = total_hours / num_days

View File

@@ -7,88 +7,19 @@ import string
import sys
def parse_foods_file():
path = Path.home() / 'projects' / 'open-journal' / 'foods'
text = path.read_text()
foods, recipes = text.split('---')
do_yesterday = len(sys.argv) > 1
def parse_macro(macro):
if macro == '...':
return ('INVALID', 0.0)
name, value = macro.split()
value = float(value.removesuffix('g').removesuffix('kcal'))
return (name, value)
foods = {
macros[0]: dict(parse_macro(macro) for macro in macros[1:])
for macros in [food.split('\n') for food in foods.strip().split('\n\n')]
}
def combine_values(fst, snd):
result = fst.copy()
for k,v in snd.items():
if k in fst:
result[k] += v
else:
result[k] = v
return result
def evaluate_ingredients(ingredients):
result = {}
total_weight = 0.0
for ingredient in ingredients:
k,v = parse_macro(ingredient)
if k == 'TOTAL':
result[k] = v
break
else:
total_weight += v
food = foods[k]
for kk,vv in food.items():
if kk not in result:
result[kk] = 0.0
result[kk] += vv * (v/100.0)
if 'TOTAL' not in result:
result['TOTAL'] = total_weight
return result
recipes = {
ingredients[0]: evaluate_ingredients(ingredients[1:])
for ingredients in [
recipe.split('\n') for recipe in recipes.strip().split('\n\n')
]
}
def get_calories_from_macros(mm):
calories = 0.0
for k,v in mm.items():
calories += v * {
'Carbs': 4,
'Fat': 9,
'Protein': 4
}.get(k, 0.0)
return calories
#for k,v in foods.items():
# print(round(v.get('Energy') - get_calories_from_macros(v)), k)
return foods, recipes
from common import parse_foods_file
foods, recipes = parse_foods_file()
entry_re = re.compile(r'^(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}) ', re.MULTILINE)
diet_re = re.compile(r'@diet (\d+g) ([a-zA-Z]+)')
day_index = -2 if do_yesterday else -1
current_day = list(sorted((Path.home() / 'workspace' /
'journal').glob('*.md')))[-1]
'journal').glob('*.md')))[day_index]
header, *tmp = entry_re.split(current_day.read_text())
entries = list(zip(tmp[::2], tmp[1::2]))