diff --git a/analyze.py b/analyze.py index d0a2a8f..71a4465 100644 --- a/analyze.py +++ b/analyze.py @@ -129,6 +129,8 @@ diet_csv = [[ 'saturated_fat', 'sugar', 'fiber' ]] +output = open('diet', 'w') + for fpath in sorted((Path.home() / 'workspace' / 'journal').glob('*.md')): day = fpath.stem header, *tmp = entry_re.split(fpath.read_text()) @@ -143,6 +145,7 @@ for fpath in sorted((Path.home() / 'workspace' / 'journal').glob('*.md')): daily_sugar = 0.0 for (timestamp, content) in sorted(entries, key=lambda x: x[0]): + ts_str = timestamp timestamp = int(datetime.strptime(timestamp, '%Y-%m-%d %H:%M:%S').timestamp()) content = '\n'.join( @@ -152,8 +155,11 @@ for fpath in sorted((Path.home() / 'workspace' / 'journal').glob('*.md')): for diet in diet_re.finditer(content): value, name = diet.groups() + output.write(f'{ts_str} {name} {value}\n') + value = float(value.removesuffix('g')) + if name in recipes: food = recipes[name] @@ -171,6 +177,7 @@ for fpath in sorted((Path.home() / 'workspace' / 'journal').glob('*.md')): print(f'ERROR: Invalid diet entry: {content}') continue + diet_csv.append(( timestamp, diff --git a/foods b/foods index 61c3b52..5b891da 100644 --- a/foods +++ b/foods @@ -1,4 +1,32 @@ +Apple +Energy 52kcal +Fat 0.2g +Carbs 14g +Fiber 2.4g +Sugar 10g +Protein 0.3g + +HarkisRouheseos +Energy 335kcal +Fat 3.1g +SaturatedFat 0.3g +Carbs 26g +Sugar 3.7g +Fiber 12g +Protein 47g +Salt 1.0g + +HazelnutIcecream +Energy 288kcal +Fat 18g +SaturatedFat 5.6g +Carbs 27g +Sugar 24g +Fiber 1.1g +Protein 4.0g +Salt 0.17g + CheesePizza Energy 210kcal Fat 5.3g @@ -129,6 +157,15 @@ Fiber 2.4g Protein 11.4g Salt 0.01g +GrahamFlour +Energy 330kcal +Fat 3.0g +SaturatedFat 0.5g +Carbs 56g +Sugar 0.8g +Fiber 13g +Protein 14g + Salt Energy 0kcal Salt 100g @@ -554,6 +591,12 @@ Carbs 1.5g Protein 22.0g Salt 2.7g +Salmon +Energy 208kcal +Fat 13g +SaturatedFat 3.0g +Protein 20g + --- Omlette @@ -590,6 +633,10 @@ FakeChicken 250g Oil 40g PeasCornPepper 225g +OilVeggies +Oil 30g +PeasCornPepper 220g + MashedPotato Margarine 40g Milk 180g @@ -633,6 +680,11 @@ FoodCream 450g Oil 50g Potato 1500g +CreamPotatoes2 +Cheese 200g +FoodCream 450g +Potato 1020g + ProteinSpaghettiBolognese ProteinSpaghetti 500g PastaSauce 500g @@ -666,7 +718,25 @@ Oil 20g SaladDressing 65g TOTAL 1675g +PastaSalad2 +TricolorePasta 500g +Salad 270g +Oil 30g +SaladDressing 100g +TOTAL 1675g + ProteinShake SkimMilk 400g ProteinPowder 60g +MashedPotato2 +Margarine 20g +Potato 760g +Milk 300g + +VegeKotlet +Oil 45g +HarkisRouheseos 180g +Milk 240g +Egg 65g +GrahamFlour 60g diff --git a/parse.py b/parse.py new file mode 100644 index 0000000..e779b37 --- /dev/null +++ b/parse.py @@ -0,0 +1,105 @@ +from datetime import datetime, timedelta +from pathlib import Path +from subprocess import run +from typing import Union +import sys +import re + +JOURNAL_PATH = '/Users/olari/workspace/journal' + +entry_re = re.compile(r'^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}') + +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' + ] + + 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() + } + +header_modules = { + 'godword': (None, parse_godword), + 'habits': (None, parse_habits), +} + +def parse_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_module(module) for module in modules) + } + +def parse_timestamp(timestamp): + return datetime.strptime('%Y-%m-%d %H:%M:%S') + +def parse_entry(entry): + + return { + + } + +def parse_file(fpath): + header = {} + entries = [] + + buf = [] + + with open(fpath) as fp: + for i, line in enumerate(fp): + if entry_re.match(line): + if not header: + header = parse_header('\n'.join([c.strip() for c in buf])) + header['line'] = i + else: + entries.append(parse_entry('\n'.join(buf))) + buf = [line] + else: + buf.append(line) + + result = { + 'header': header, + 'entries': entries, + } + + breakpoint() + + return result + +def parse_journal(): + return { + info['header']['title']: info + for fpath in get_daily_file_paths()[-5:] + for info in parse_file(fpath) + } + +import json +open('journal.json', 'w').write(json.dumps(parse_journal())) diff --git a/search.py b/search.py new file mode 100644 index 0000000..8537e68 --- /dev/null +++ b/search.py @@ -0,0 +1,29 @@ +from pathlib import Path +from subprocess import run +import re +import sys + +entry_re = re.compile(r'^(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})', re.MULTILINE) + +matches = [] + +for fpath in sorted((Path.home() / 'workspace' / 'journal').glob('*.md')): + header, *tmp = entry_re.split(fpath.read_text()) + entries = list(zip(tmp[::2], tmp[1::2])) + + for (timestamp, content) in sorted(entries, key=lambda x: x[0]): + content = '\n'.join( + part.replace('\n', ' ') + for part in content.split('\n\n') + ) + + if sys.argv[1].lower() in content.lower().split(): + matches.append((timestamp, content)) + +buf = '' + +for (ts, c) in matches: + c = c.replace('\n', ' ').strip() + buf += (f'[[{ts}]] {c}')[:80] + '\n' + +run(['nvim', '-'], input=buf.encode('utf-8')) diff --git a/summary.py b/summary.py new file mode 100644 index 0000000..9f36fc7 --- /dev/null +++ b/summary.py @@ -0,0 +1,151 @@ +from pathlib import Path +from datetime import datetime +from collections import Counter +from functools import reduce +import re +import string + +import sys + +def parse_foods_file(): + path = Path.home() / 'projects' / 'open-journal' / 'foods' + text = path.read_text() + foods, recipes = text.split('---') + + 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 + +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]+)') + +current_day = list(sorted((Path.home() / 'workspace' / + 'journal').glob('*.md')))[-1] + +header, *tmp = entry_re.split(current_day.read_text()) +entries = list(zip(tmp[::2], tmp[1::2])) + +daily_grams = 0.0 +daily_calories = 0.0 +daily_protein = 0.0 + +for (timestamp, content) in sorted(entries, key=lambda x: x[0]): + content = '\n'.join( + part.replace('\n', ' ') + for part in content.split('\n\n') + ) + + has_printed = False + entry_calories = 0.0 + entry_protein = 0.0 + for diet in diet_re.finditer(content): + if not has_printed: + print(f'-- {timestamp}') + has_printed = True + + value, name = diet.groups() + value = float(value.removesuffix('g')) + + if name in recipes: + food = recipes[name] + + if value == 0.0: + value = food['TOTAL'] + + food = {k: v*(value/food['TOTAL']) for k,v in food.items()} + elif name in foods: + if value == 0.0: + value = 100 + + food = {k: v*(value/100.0) for k,v in foods[name].items()} + else: + breakpoint() + print(f'ERROR: Invalid diet entry: {content}') + continue + + protein = round(food.get('Protein', 0.0), 2) + calories = round(food.get('Energy', 0.0), 2) + + entry_calories += calories + entry_protein += protein + + print(f'{name:<20} {value:<6}g, {calories:<6}kcal, {protein:<6}g protein') + + if has_printed: + entry_calories = round(entry_calories, 2) + entry_protein = round(entry_protein, 2) + print(f'-- TOTAL: {entry_calories}kcal, {entry_protein}g protein') + print() + + daily_calories += entry_calories + daily_protein += entry_protein + +print(f'-- DAILY TOTAL ({daily_calories}kcal, {daily_protein}g protein)')