grasp/server/org_tools.py

187 lines
4.4 KiB
Python

from datetime import datetime
import re
from typing import List, Optional
DEFAULT_TEMPLATE = "* %U %:description %:tags\n%:link\n%:initial\n"
# TODO reuse inorganic/orgparse??
def date2org(t: datetime) -> str:
return t.strftime("%Y-%m-%d %a")
def datetime2orgtime(t: datetime) -> str:
return t.strftime("%H:%M")
def datetime2org(t: datetime) -> str:
return date2org(t) + " " + datetime2orgtime(t)
def empty(s) -> bool:
return s is None or len(s.strip()) == 0
# TODO protocol??
# TODO put template in config??
class Config:
@staticmethod
def format_selection(selection: str) -> List[str]: # type: ignore[empty-body]
...
@staticmethod
def format_comment(comment: str) -> List[str]: # type: ignore[empty-body]
...
class DefaultConfig(Config):
@staticmethod
def format_selection(selection: str) -> List[str]:
return [
'Selection:',
selection,
]
@staticmethod
def format_comment(comment: str) -> List[str]:
return [
'Comment:',
comment
]
def as_org(
url: str,
title: str,
selection: str,
comment: str,
tags: List[str],
org_template: str,
config: Optional[Config] = None,
*,
_now=None,
):
"""
Formats captured results according to org template. Supports all sensible (e.g. non-interactive) template expansions from https://orgmode.org/manual/Template-expansion.html#Template-expansion.
Additionally, supports :selection, :comment and :tags expansions.
You can look at `test_templates` for some specific examples.
"""
py_template = re.sub(r'([^\\])%[:]?([?\w]+)', r'\1{\2}', org_template)
# replace all escaped % with regular %, safe to do after substitution
py_template = py_template.replace(r'\%', '%')
NOW = datetime.now() if _now is None else _now
org_date = date2org(NOW)
org_datetime = datetime2org(NOW)
tags_s = '' if len(tags) == 0 else (':' + ':'.join(tags) + ':')
if config is None:
config = DefaultConfig()
# TODO tabulate selection and comments?
# TODO not sure, maybe add as org quote?
parts = []
if not empty(selection):
parts.extend(config.format_selection(selection))
if not empty(comment):
parts.extend(config.format_comment(comment))
initial = '\n'.join(parts)
res = py_template.format(
t=f'<{org_date}>',
u=f'[{org_date}]',
T=f'<{org_datetime}>',
U=f'[{org_datetime}]',
description=title,
link=url,
initial=initial,
i=initial,
tags=tags_s,
selection=selection,
comment=comment,
annotation=f'[[{url}][{title}]]', # https://orgmode.org/manual/capture-protocol.html
**{'?': ''}, # just ignore it
)
return res
def test_templates() -> None:
url = 'https://whatever'
title = 'hello'
selection = """some
selected
text
"""
comment = 'fafewfewf'
tags = ['aba', 'caba']
vimzettel_template = r'zettel \%title [%:description] testing'
# https://orgmode.org/guide/Capture-templates.html
org_templates = [
DEFAULT_TEMPLATE,
"* %? [[%:link][%:description]] \nCaptured On: %U",
"* %:annotation",
"* %U %:description :protocol: \n %:link \n%:initial \n\n",
"* %t captured stuff %:tags \n ",
"\n** Selection\n%:selection\n** Comment\n%:comment\n",
"""* TODO [#A] %:link
** Selection
%:selection
** Comment
%:comment
""",
"""* %T %:link %:description
#+BEGIN_QUOTE
%:selection
#+END_QUOTE
""",
vimzettel_template,
]
# check they aren't crashing
for org_template in org_templates:
res = as_org(
url,
title,
selection,
comment,
tags,
org_template=org_template
)
print()
print(res)
res = as_org(
url,
title,
selection,
comment,
tags,
org_template=DEFAULT_TEMPLATE,
_now=datetime(1111, 11, 11),
)
expected = '''
* [1111-11-11 Sat 00:00] hello :aba:caba:
https://whatever
Selection:
some
selected
text
Comment:
fafewfewf
'''.lstrip()
assert res == expected
res = as_org(
url,
title,
selection,
comment,
tags,
org_template=vimzettel_template,
)
assert res == 'zettel %title [hello] testing'