Files
obsidian_utils/dailySummary/utils/obsidian_writer.py
T
fegger 4b7ddcb1c1 Add daily coding summary tool for Obsidian
Integrates with Zed editor history and Git commits to track daily
coding activity. Writes idempotent `## Coding Work` sections into
Obsidian daily notes with project breakdowns and commit stats.
2026-05-26 14:52:09 +02:00

113 lines
3.6 KiB
Python

"""Write or append daily summaries to Obsidian notes."""
from datetime import datetime, timezone
from pathlib import Path
from typing import List
from .git_tracker import RepoSummary
from .zed_tracker import ProjectSummary
DAILY_NOTE_FORMAT = "%Y-%m-%d"
SECTION_HEADER = "## Coding Work"
def _format_time(ts: int) -> str:
"""Format a Unix timestamp as HH:MM."""
dt = datetime.fromtimestamp(ts, tz=timezone.utc).astimezone()
return dt.strftime("%H:%M")
def generate_markdown(
zed_projects: List[ProjectSummary],
git_repos: List[RepoSummary],
date: datetime,
) -> str:
"""Generate the markdown content for the daily summary."""
lines = []
lines.append(SECTION_HEADER)
lines.append("")
if not zed_projects and not git_repos:
lines.append("No coding activity tracked today. 🌴")
return "\n".join(lines)
if zed_projects:
lines.append("### Zed Editors")
lines.append("")
for project in zed_projects:
langs = (
f" ({', '.join(sorted(project.languages))})"
if project.languages
else ""
)
lines.append(
f"- **{project.project}**{langs}{len(project.files)} file(s)"
)
# Show up to 5 most recent files
for path, mtime in project.files[:5]:
time_str = _format_time(mtime)
lines.append(f" - `{Path(path).name}` at {time_str}")
if len(project.files) > 5:
lines.append(f" - *…and {len(project.files) - 5} more*")
lines.append("")
if git_repos:
lines.append("### Git Commits")
lines.append("")
for repo in git_repos:
lines.append(
f"- **{repo.name}** — {len(repo.commits)} commit(s), "
f"+{repo.total_insertions}/-{repo.total_deletions} lines, "
f"{repo.total_files} file(s)"
)
for commit in repo.commits:
time_str = _format_time(commit.timestamp)
lines.append(f" - `{commit.hash}` {commit.message}{time_str}")
lines.append("")
return "\n".join(lines)
# Backward compatibility alias
_generate_markdown = generate_markdown
def write_daily_summary(
vault_path: Path,
zed_projects: List[ProjectSummary],
git_repos: List[RepoSummary],
daily_folder: str = "Daily Notes",
date: datetime = None,
) -> Path:
"""Write or append a coding summary to the Obsidian daily note."""
if date is None:
date = datetime.now(timezone.utc).astimezone()
note_dir = vault_path / daily_folder
note_dir.mkdir(parents=True, exist_ok=True)
note_name = date.strftime(DAILY_NOTE_FORMAT)
note_path = note_dir / f"{note_name}.md"
content = _generate_markdown(zed_projects, git_repos, date)
if note_path.exists():
existing = note_path.read_text(encoding="utf-8")
# If section already exists, replace it; otherwise append
if SECTION_HEADER in existing:
before, _, after = existing.partition(SECTION_HEADER)
# Find the next section header or end of file
next_section_idx = after.find("\n## ")
if next_section_idx != -1:
after = after[next_section_idx:]
else:
after = ""
new_content = before.rstrip() + "\n\n" + content + "\n" + after.lstrip()
else:
new_content = existing.rstrip() + "\n\n" + content + "\n"
else:
new_content = f"# {note_name}\n\n" + content + "\n"
note_path.write_text(new_content, encoding="utf-8")
return note_path