simplify and start adapting to data provider

This commit is contained in:
Dima Gerasimov 2019-07-04 08:26:31 +01:00
parent 78a8bbdc77
commit c10141411b

View file

@ -1,23 +1,26 @@
#!/usr/bin/env python3.6 #!/usr/bin/env python3
from config import IGNORED, BACKUPS_PATH # pip3 install icalendar
from config import RTM_API_KEY, RTM_API_TOKEN, RTM_API_SECRET
from collections import deque
import logging import logging
import os import os
import re import re
from collections import deque
from pathlib import Path
from sys import argv from sys import argv
from typing import Dict, List, Optional, TypeVar
from kython import * from kython.klogging import LazyLogger
from kython import group_by_key
from kython import kompress
import icalendar # type: ignore import icalendar # type: ignore
from icalendar.cal import Todo # type: ignore from icalendar.cal import Todo # type: ignore
def extract_backup_date(s: str): logger = LazyLogger('rtm-provider')
s = s[len(BACKUPS_PATH + "/rtm_"):-4]
s = s.replace('_', '-')
return parse_date(s, dayfirst=False) def get_last_backup():
return max(Path('***REMOVED***').glob('*.ical*'))
# TODO extract in a module to parse RTM's ical? # TODO extract in a module to parse RTM's ical?
@ -37,15 +40,17 @@ class MyTodo:
[tags_str] = re.findall(r'\nTags:(.*?)\n', desc, flags=re.DOTALL) [tags_str] = re.findall(r'\nTags:(.*?)\n', desc, flags=re.DOTALL)
self.tags = [t.strip() for t in tags_str.split(',')] # TODO handle none? self.tags = [t.strip() for t in tags_str.split(',')] # TODO handle none?
# TODO use caching wrapper
# TODO use decorator that stores cache in the object itself?
def get_notes(self) -> List[str]: def get_notes(self) -> List[str]:
if self.notes is None: if self.notes is None:
self._init_notes() self._init_notes()
return self.notes return self.notes # type: ignore
def get_tags(self) -> List[str]: def get_tags(self) -> List[str]:
if self.tags is None: if self.tags is None:
self._init_tags() self._init_tags()
return self.tags return self.tags # type: ignore
def get_uid(self) -> str: def get_uid(self) -> str:
return str(self.todo['UID']) return str(self.todo['UID'])
@ -55,7 +60,8 @@ class MyTodo:
def get_status(self) -> str: def get_status(self) -> str:
if 'STATUS' not in self.todo: if 'STATUS' not in self.todo:
return None # TODO 'COMPLETED'? return None # type: ignore
# TODO 'COMPLETED'?
return str(self.todo['STATUS']) return str(self.todo['STATUS'])
def get_time(self): def get_time(self):
@ -84,10 +90,10 @@ class RtmBackup:
self.revision = revision self.revision = revision
@staticmethod @staticmethod
def from_path(path: str) -> 'RtmBackup': def from_path(path: Path) -> 'RtmBackup':
with open(path, 'rb') as fo: with kompress.open(path, 'rb') as fo:
data = fo.read() data = fo.read()
revision = extract_backup_date(path) revision = 'TODO FIXME' # extract_backup_date(path)
return RtmBackup(data, revision) return RtmBackup(data, revision)
def get_all_todos(self) -> List[MyTodo]: def get_all_todos(self) -> List[MyTodo]:
@ -104,150 +110,11 @@ class RtmBackup:
return group_by_key(todos, lambda todo: todo.get_title()) return group_by_key(todos, lambda todo: todo.get_title())
# TODO move to kython?
def group_by_any(items: List[T], key1, key2):
kk1 = group_by_key(items, key1)
kk2 = group_by_key(items, key2)
used: Set[T] = set()
def bfs(i):
queue = deque()
res = []
def register(i):
if i not in used:
res.append(i)
used.add(i)
queue.append(i)
register(i)
while len(queue) > 0:
top = queue.popleft()
for ni in kk1[key1(top)]:
register(ni)
for ni in kk2[key2(top)]:
register(ni)
return res
groups = []
for i in items:
if i not in used:
groups.append(bfs(i))
return groups
def check_wiped_notes(backups: List[str]):
all_todos = []
for b in backups:
backup = RtmBackup.from_path(b)
all_todos.extend(backup.get_all_todos())
# first, let tasks with same titles or uids be in the same class. (if we rename a task, it retains UID)
groups = group_by_any(all_todos, lambda k: k.get_title(), lambda k: k.get_uid())
kk_map = {i: g for i, g in enumerate(groups)} # type: Dict[int, List[MyTodo]]
def has_safe_tag(todos: List[MyTodo]) -> bool:
all_tags = set.union(*(set(todo.get_tags()) for todo in todos))
return 'z_dn_safe' in all_tags or 'y_see_org' in all_tags
def has_safe_note(todo: MyTodo) -> bool:
notes = todo.get_notes()
for note in notes:
if 'is_safe' in note:
return True
return False
kk_map = {kk: todos for kk, todos in kk_map.items() if not has_safe_tag(todos)}
def boring(todos: List[MyTodo]) -> bool:
if len(todos) <= 1:
return True
counts = [len(todo.get_notes()) for todo in todos]
if counts.count(counts[0]) == len(counts):
return True
if all(a <= b for a, b in zip(counts, counts[1:])):
# increasing
return True
for tags in [todo.get_tags() for todo in todos]:
if 'routine' in tags: # TODO FIXME 'safe' tag. If a task has it in ANY of the backups, it is considered safe. tag is better for completed
return True
# good thing about safe note is if it disappears, we'll notice it!
if has_safe_note(todos[-1]):
return True
return False
bads = []
for kk, todos in sorted(kk_map.items()):
# TODO sorts by 1) competion 2) modified date
todos = sorted(todos, key = MyTodo.alala_key)
if not boring(todos):
for todo in todos:
bads.append(todo)
if len(bads) != 0:
logging.error('\n'.join(set(t.get_title() for t in bads)))
for t in bads:
logging.error(f"{t.revision} {t.get_title()} {t.get_uid()} {t.get_notes()}")
def are_suspicious(l: List[MyTodo]) -> bool:
if len(l) <= 1: # probably not repeating
return False
all_tags = set.union(*(set(todo.get_tags()) for todo in l))
if 'z_ac_safe' in all_tags:
return False
suspicious = 0
for c in l:
if c.is_completed():
suspicious += 1
elif 'DUE' not in c.todo:
suspicious += 1
return len(l) == suspicious
def check_accidentally_completed(path: str):
backup = RtmBackup.from_path(path)
groups = backup.get_todos_by_title()
susp = []
for k, g in sorted(groups.items()):
if k in IGNORED:
logging.info(k + " is ignored, skipping...")
continue
if are_suspicious(g):
susp.append(f'"{k}",')
logging.error(k)
if len(susp) > 0:
print("Suspicious:")
for s in susp:
print(s)
else:
print("Nothing suspicious!")
def main(): def main():
backups = sorted([os.path.join(BACKUPS_PATH, p) for p in os.listdir(BACKUPS_PATH) if p.endswith('ical')], key=extract_backup_date) backup = RtmBackup.from_path(get_last_backup())
last_backup = backups[-1] for t in backup.get_all_todos():
print(t)
check_accidentally_completed(last_backup)
logging.info("Using " + last_backup + " for checking for accidentally completed notes")
backups = backups[:-5:5] + backups[-5:] # always include last 5 # TODO FIXME USE ALL?
logging.info(f"Using {backups} for checking for wiped notes")
check_wiped_notes(backups)
if __name__ == '__main__': if __name__ == '__main__':
setup_logging()
main() main()