Last active
September 8, 2025 00:33
-
-
Save wrouesnel/3971a17315231c87a290d1c2bbc40693 to your computer and use it in GitHub Desktop.
Very simple worklog script for CLI usage
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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