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 **/*.csv
.ipynb_checkpoints/ .ipynb_checkpoints/
__pycache__/ __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 ProteinRakha
Energy 67kcal Energy 67kcal
Fat 0.2g Fat 0.2g
@@ -693,6 +751,10 @@ Milk 180g
Potato 1000g Potato 1000g
TOTAL 1300g TOTAL 1300g
MashedPotato3
Potato 1050g
Milk 350g
Blov Blov
BrownRice 600g BrownRice 600g
Ketchup 250g Ketchup 250g
@@ -791,6 +853,12 @@ Milk 240g
Egg 65g Egg 65g
GrahamFlour 60g GrahamFlour 60g
VegeKotlet2
HarkisRouheseos 200g
Milk 450g
WholeGrainBread 50g
Flour 90g
RoastedVeggies2 RoastedVeggies2
Broccoli 300g Broccoli 300g
Oil 70g Oil 70g
@@ -804,9 +872,23 @@ PeasCornPepper 200g
Ketchup 100g Ketchup 100g
TOTAL 760g TOTAL 760g
BeanitHarkisVeggies2
Oil 40g
BeanitHarkis 250g
PeasCornPepper 220g
Ketchup 100g
TOTAL 740g
Pancake Pancake
SkimMilk 430g SkimMilk 430g
Water 300g Water 300g
Oil 30g Oil 30g
Flour 430g Flour 430g
Sugar 20g 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 pathlib import Path
from subprocess import run from datetime import datetime
from typing import Union
import sys
import re 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: def parse_godword(godword):
weekdays = [ return godword.split()
'monday', 'tuesday', 'wednesday', 'thursday',
'friday', 'saturday', 'sunday'
]
if expr == 'today': def parse_habits(habits):
return now result = {}
elif expr == 'tomorrow': for habit in habits.splitlines():
return now + timedelta(days=1) value, name = habit.split(maxsplit=1)
elif expr == 'yesterday': name = name.strip()
return now - timedelta(days=1) result[name] = value[1] == 'x'
elif expr in weekdays: return result
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 = { header_modules = {
'godword': (None, parse_godword), 'godword': parse_godword,
'habits': (None, parse_habits), '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): def parse_header(header):
title, *modules = header.split('\n\n') result = {}
title = title.removeprefix('# ')
return {
'title': title,
'modules': dict(parse_header_module(module) for module in modules)
}
def parse_diet(content): def split_into_blocks(text):
pass return [b.strip() for b in re.split(r'\n{2,}', text) if b.strip() != '']
def parse_content(content): title, *modules = split_into_blocks(header)
content = content.strip()
return { for module in modules:
'blocks': [b.replace('\n', ' ') for b in content.split('\n\n')] name, value = module.split('\n', maxsplit=1)
} name = name.lower().removesuffix(':')
result[name] = header_modules[name](value)
return result
def parse_timestamp(timestamp): def parse_timestamp(timestamp):
return datetime.strptime(timestamp, '%Y-%m-%d %H:%M:%S') 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): 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 = {} result = {}
for fpath in get_daily_file_paths()[-5:]: def split_into_blocks(text):
info = parse_file(fpath) result = []
result[info['header']['title']] = info
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 return result
import json timestamp, content = entry
open('journal.json', 'w').write(json.dumps(parse_journal()))
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
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() 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() lines = content.splitlines()
i = 0
current_chapter = '' current_chapter = ''
result = defaultdict(float)
total_chapters = 0 total_chapters = 0
completed_chapters = 0 completed_chapters = set()
today = datetime.now().replace(hour=0,minute=0,second=0,microsecond=0) today = datetime.now().replace(hour=0,minute=0,second=0,microsecond=0)
this_week = today - timedelta(days=7) this_week = today - timedelta(days=7)
@@ -38,13 +22,17 @@ week_hours = 0.0
oldest_timestamp = datetime.now() oldest_timestamp = datetime.now()
i = 0
while i < len(lines): while i < len(lines):
line = lines[i].strip() line = lines[i].strip()
if line.startswith('#'): if line.startswith('#'):
current_chapter = line[line.find(' ')+1:] current_chapter = line[line.find(' ')+1:]
total_chapters += 1 total_chapters += 1
elif line.startswith('@start'): else:
completed_chapters.add(current_chapter)
if line.startswith('@start'):
start = parse_timestamp(line.removeprefix('@start ')) start = parse_timestamp(line.removeprefix('@start '))
if start < oldest_timestamp: if start < oldest_timestamp:
@@ -54,28 +42,18 @@ while i < len(lines):
line = lines[i].strip() line = lines[i].strip()
end = parse_timestamp(line.removeprefix('@stop ')) end = parse_timestamp(line.removeprefix('@stop '))
delta = end - start delta = end - start
hours = delta.seconds / 60 / 60 hours = delta.seconds / 60 / 60
result[current_chapter] += hours
total_hours += hours total_hours += hours
if start > this_week: if start > this_week:
week_hours += hours week_hours += hours
if start > today: if start > today:
day_hours += hours day_hours += hours
elif line.startswith('@done'):
completed_chapters += 1
i += 1 i += 1
#from pprint import pprint completed_chapters = len(completed_chapters)
#pprint(dict(result), sort_dicts=False)
num_days = (datetime.now() - oldest_timestamp).days or 1 num_days = (datetime.now() - oldest_timestamp).days or 1
hours_per_day = total_hours / num_days hours_per_day = total_hours / num_days

View File

@@ -7,88 +7,19 @@ import string
import sys import sys
def parse_foods_file(): do_yesterday = len(sys.argv) > 1
path = Path.home() / 'projects' / 'open-journal' / 'foods'
text = path.read_text()
foods, recipes = text.split('---')
def parse_macro(macro): from common import parse_foods_file
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() foods, recipes = parse_foods_file()
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}) ', re.MULTILINE)
diet_re = re.compile(r'@diet (\d+g) ([a-zA-Z]+)') 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' / 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()) header, *tmp = entry_re.split(current_day.read_text())
entries = list(zip(tmp[::2], tmp[1::2])) entries = list(zip(tmp[::2], tmp[1::2]))