Initial data provider for roam research
This commit is contained in:
parent
e884d90ea0
commit
575de57fb6
1 changed files with 110 additions and 0 deletions
110
my/roamresearch.py
Normal file
110
my/roamresearch.py
Normal file
|
@ -0,0 +1,110 @@
|
||||||
|
"""
|
||||||
|
[[Roam][https://roamresearch.com]] data
|
||||||
|
"""
|
||||||
|
from datetime import datetime
|
||||||
|
from pathlib import Path
|
||||||
|
from itertools import chain
|
||||||
|
from typing import NamedTuple, Iterator, List, Optional
|
||||||
|
|
||||||
|
import pytz
|
||||||
|
|
||||||
|
from .common import get_files, LazyLogger, Json
|
||||||
|
|
||||||
|
from my.config import roamresearch as config
|
||||||
|
|
||||||
|
logger = LazyLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def last() -> Path:
|
||||||
|
return max(get_files(config.export_path, '*.json'))
|
||||||
|
|
||||||
|
|
||||||
|
class Keys:
|
||||||
|
CREATED = 'create-time'
|
||||||
|
EDITED = 'edit-time'
|
||||||
|
EDIT_EMAIL = 'edit-email'
|
||||||
|
STRING = 'string'
|
||||||
|
CHILDREN = 'children'
|
||||||
|
TITLE = 'title'
|
||||||
|
|
||||||
|
|
||||||
|
class Node(NamedTuple):
|
||||||
|
raw: Json
|
||||||
|
|
||||||
|
# TODO not sure if UTC
|
||||||
|
@property
|
||||||
|
def created(self) -> datetime:
|
||||||
|
ct = self.raw.get(Keys.CREATED)
|
||||||
|
if ct is None:
|
||||||
|
# e.g. daily notes don't have create time for some reason???
|
||||||
|
return self.edited
|
||||||
|
else:
|
||||||
|
return datetime.fromtimestamp(ct / 1000, tz=pytz.utc)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def edited(self) -> datetime:
|
||||||
|
rt = self.raw[Keys.EDITED]
|
||||||
|
return datetime.fromtimestamp(rt / 1000, tz=pytz.utc)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def title(self) -> Optional[str]:
|
||||||
|
return self.raw.get(Keys.TITLE)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def body(self) -> Optional[str]:
|
||||||
|
return self.raw.get(Keys.STRING)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def children(self) -> List['Node']:
|
||||||
|
ch = self.raw.get(Keys.CHILDREN, [])
|
||||||
|
return list(map(Node, ch))
|
||||||
|
|
||||||
|
def _render(self) -> Iterator[str]:
|
||||||
|
ss = f'[{self.created:%Y-%m-%d %H:%M}] {self.title or " "}'
|
||||||
|
body = self.body
|
||||||
|
sc = chain.from_iterable(c._render() for c in self.children)
|
||||||
|
|
||||||
|
yield ss
|
||||||
|
if body is not None:
|
||||||
|
yield body
|
||||||
|
for c in sc:
|
||||||
|
yield '| ' + c
|
||||||
|
|
||||||
|
def render(self) -> str:
|
||||||
|
return '\n'.join(self._render())
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f'Node(created={self.created}, title={self.title}, body={self.body})'
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def make(raw: Json) -> Iterator['Node']:
|
||||||
|
is_empty = set(raw.keys()) == {Keys.EDITED, Keys.EDIT_EMAIL, Keys.TITLE}
|
||||||
|
# not sure about that... but daily notes end up like that
|
||||||
|
if is_empty:
|
||||||
|
# todo log?
|
||||||
|
return
|
||||||
|
yield Node(raw)
|
||||||
|
|
||||||
|
|
||||||
|
class Roam:
|
||||||
|
def __init__(self, json: List[Json]) -> None:
|
||||||
|
self.nodes: List[Node] = []
|
||||||
|
# TODO make it lazy?
|
||||||
|
for j in json:
|
||||||
|
self.nodes.extend(Node.make(j))
|
||||||
|
|
||||||
|
|
||||||
|
def roam() -> Roam:
|
||||||
|
import json
|
||||||
|
raw = json.loads(last().read_text())
|
||||||
|
roam = Roam(raw)
|
||||||
|
return roam
|
||||||
|
|
||||||
|
|
||||||
|
def print_all_notes():
|
||||||
|
# just a demo method
|
||||||
|
# TODO demonstrate dumping as org-mode??
|
||||||
|
for n in roam().nodes:
|
||||||
|
print(n.render())
|
||||||
|
|
||||||
|
# TODO could generate org-mode mirror in a single file for a demo?
|
Loading…
Add table
Reference in a new issue