- Add joplin.py with fetch_week_note() that walks Work > Timesheets > YYYY and returns the body of the matching YYYY-WNN note via joppy ClientApi - Add filter_rows_by_date() to parser.py to extract only rows belonging to a specific day based on '# ... YYYY-MM-DD' headings in the document - Update cli.py: input and --joplin are now a mutually exclusive required group; add --token flag with JOPLIN_TOKEN env var fallback; --date is parsed into a real date object used for both output and day filtering - Add joppy as a runtime dependency (lazy-imported in cli.py) - Add tests for filter_rows_by_date and full mocked coverage of joplin.py - Update AGENTS.md with Joplin usage, notebook structure, and test rules The actual Joplin structure has notes directly inside the year notebook (Work > Timesheets > YYYY), not in per-week sub-notebooks as initially assumed. fetch_week_note() reflects this flat structure.
68 lines
2 KiB
Python
68 lines
2 KiB
Python
"""
|
|
Joplin integration via the joppy ClientApi.
|
|
|
|
Actual notebook structure:
|
|
Work > Timesheets > YYYY
|
|
|
|
Notes live directly inside the year notebook and are titled 'YYYY - WNN'.
|
|
"""
|
|
|
|
from datetime import date
|
|
from typing import Optional
|
|
|
|
from joppy.client_api import ClientApi
|
|
|
|
|
|
def _iso_week_label(d: date) -> str:
|
|
"""Return the note title for the week containing the given date, e.g. '2026 - W21'."""
|
|
year, week, _ = d.isocalendar()
|
|
return f"{year} - W{week:02d}"
|
|
|
|
|
|
def _find_notebook(
|
|
api: ClientApi, title: str, parent_id: Optional[str] = None
|
|
) -> Optional[str]:
|
|
"""Return the ID of a notebook matching title (and optionally parent_id), or None."""
|
|
for nb in api.get_all_notebooks():
|
|
if nb.title == title:
|
|
if parent_id is None or nb.parent_id == parent_id:
|
|
return nb.id
|
|
return None
|
|
|
|
|
|
def fetch_week_note(token: str, target_date: date) -> str:
|
|
"""
|
|
Fetch the body of the weekly timesheet note from Joplin for the week
|
|
containing target_date.
|
|
|
|
Notebook path: Work > Timesheets > YYYY
|
|
Note title: YYYY - WNN
|
|
|
|
Raises RuntimeError if any notebook or the note cannot be found.
|
|
"""
|
|
api = ClientApi(token=token)
|
|
week_label = _iso_week_label(target_date)
|
|
year_str = str(target_date.year)
|
|
|
|
work_id = _find_notebook(api, "Work")
|
|
if work_id is None:
|
|
raise RuntimeError("Joplin notebook 'Work' not found")
|
|
|
|
timesheets_id = _find_notebook(api, "Timesheets", parent_id=work_id)
|
|
if timesheets_id is None:
|
|
raise RuntimeError("Joplin notebook 'Work > Timesheets' not found")
|
|
|
|
year_id = _find_notebook(api, year_str, parent_id=timesheets_id)
|
|
if year_id is None:
|
|
raise RuntimeError(
|
|
f"Joplin notebook 'Work > Timesheets > {year_str}' not found"
|
|
)
|
|
|
|
notes = api.get_all_notes(notebook_id=year_id, fields="id,title,body")
|
|
for note in notes:
|
|
if note.title == week_label:
|
|
return note.body
|
|
|
|
raise RuntimeError(
|
|
f"Joplin note '{week_label}' not found in 'Work > Timesheets > {year_str}'"
|
|
)
|