4b7ddcb1c1
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.
113 lines
3.6 KiB
Python
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
|