Skip to content

Instantly share code, notes, and snippets.

@wrouesnel
Last active September 8, 2025 00:33
Show Gist options
  • Save wrouesnel/3971a17315231c87a290d1c2bbc40693 to your computer and use it in GitHub Desktop.
Save wrouesnel/3971a17315231c87a290d1c2bbc40693 to your computer and use it in GitHub Desktop.
Very simple worklog script for CLI usage
#!/usr/bin/env python3
import sys
import datetime
import click
import appdirs
import io
import subprocess
from pathlib import Path
from typing import Sequence
dirs = appdirs.AppDirs("worklog")
class WorkLogException(Exception):
pass
# https://stackoverflow.com/questions/2301789/how-to-read-a-file-in-reverse-order
def reverse_readline(filename, buf_size=8192):
"""A generator that returns the lines of a file in reverse order"""
with open(filename, 'rb') as fh:
segment = None
offset = 0
fh.seek(0, io.SEEK_END)
file_size = remaining_size = fh.tell()
while remaining_size > 0:
offset = min(file_size, offset + buf_size)
fh.seek(file_size - offset)
buffer = fh.read(min(remaining_size, buf_size))
# remove file's last "\n" if it exists, only for the first buffer
if remaining_size == file_size and buffer[-1] == ord('\n'):
buffer = buffer[:-1]
remaining_size -= buf_size
lines = buffer.split('\n'.encode())
# append last chunk's segment to this chunk's last line
if segment is not None:
lines[-1] += segment
segment = lines[0]
lines = lines[1:]
# yield lines in this chunk except the segment
for line in reversed(lines):
# only decode on a parsed line, to avoid utf-8 decode error
yield line.decode()
# Don't yield None if the file was empty
if segment is not None:
yield segment.decode()
class Data():
def __init__(self, data_dir: Path, dt=None):
if dt is None:
dt = datetime.datetime.now()
self.date = dt
self.data_dir = data_dir
self.todays_ts = dt.strftime("%Y-%m-%d")
self.todays_file = self.data_dir / Path(f"worklog-{self.todays_ts}.md")
self.todays_header = f"## Work Log: {self.todays_ts}"
def exists(self):
return self.todays_file.exists()
@click.group("worklog")
@click.pass_context
def cli_worklog(ctx):
"""Simplified Work Tracking Tool"""
data_dir = Path(dirs.user_data_dir)
data_dir.mkdir(parents=True, exist_ok=True)
ctx.obj = Data(data_dir=data_dir)
@cli_worklog.command("task")
@click.argument("task", nargs=-1)
@click.pass_obj
def cli_task(obj: Data, task: Sequence[str]):
"""Add a task bullet point"""
if not obj.todays_file.exists():
obj.todays_file.write_text(obj.todays_header + "\n")
# Ensure a newline at the end of the file before adding a new task
with obj.todays_file.open("r+") as f:
f.seek(0, io.SEEK_END)
eof = f.tell()
if eof != 0:
f.seek(f.tell()-1, io.SEEK_SET)
if f.read(1) != "\n":
f.write("\n")
else:
f.write("\n")
# Append the newline
with obj.todays_file.open("at") as f:
f.write(f"* {' '.join(task)}\n")
@cli_worklog.command("open")
@click.pass_obj
def cli_open(obj: Data):
"""Open todays worklog"""
subprocess.call(["xdg-open", obj.todays_file.as_posix()])
@cli_worklog.command("summary")
@click.pass_obj
def cli_summary(obj: Data):
"""Summarize bullet points from today/yesterday"""
yesterday = obj.date - datetime.timedelta(days=1)
last_worklog = Data(data_dir=obj.data_dir, dt=yesterday)
if last_worklog.exists():
sys.stdout.write("# Yesterday\n")
with last_worklog.todays_file.open("rt") as f:
for line in f.readlines():
if line.strip() == last_worklog.todays_header:
continue
sys.stdout.write(line)
sys.stdout.write("\n")
else:
# Check last 7 days
while not last_worklog.exists() and (datetime.datetime.now() - last_worklog.date).days < 7:
yesterday = last_worklog.date - datetime.timedelta(days=1)
last_worklog = Data(data_dir=obj.data_dir, dt=yesterday)
if last_worklog.exists():
sys.stdout.write(f"# {last_worklog.date.strftime('%A')}\n")
with last_worklog.todays_file.open("rt") as f:
for line in f.readlines():
if line.strip() == last_worklog.todays_header:
continue
sys.stdout.write(line)
sys.stdout.write("\n")
if obj.todays_file.exists():
with obj.todays_file.open("rt") as f:
sys.stdout.write(f"# Today\n")
for line in f.readlines():
if line.strip() == obj.todays_header:
continue
sys.stdout.write(line)
if __name__ == "__main__":
cli_worklog(auto_envvar_prefix="WORKLOG")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment