Skip to content

Commit e7bb6b9

Browse files
committed
log following script
1 parent 0da123d commit e7bb6b9

1 file changed

Lines changed: 136 additions & 0 deletions

File tree

bin/journal-follow.py

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
#!/usr/bin/env python3
2+
3+
import argparse
4+
import json
5+
import subprocess
6+
import sys
7+
import time
8+
from datetime import UTC, datetime
9+
from pathlib import Path
10+
from typing import Any
11+
12+
13+
ROOT_DIR = Path(__file__).resolve().parent.parent
14+
TASKWARIOR = ROOT_DIR / "bin" / "taskwarior.sh"
15+
COLUMN_PADDING = 2
16+
17+
18+
def parse_args() -> argparse.Namespace:
19+
parser = argparse.ArgumentParser(description="Follow project journal records.")
20+
parser.add_argument("-n", "--lines", type=int, default=20, help="number of existing records to show")
21+
parser.add_argument("-i", "--interval", type=float, default=1.0, help="poll interval in seconds")
22+
return parser.parse_args()
23+
24+
25+
def load_records() -> list[dict[str, Any]]:
26+
result = subprocess.run(
27+
[str(TASKWARIOR), "rc.verbose:nothing", "+journal", "export"],
28+
cwd=ROOT_DIR,
29+
check=True,
30+
stdout=subprocess.PIPE,
31+
text=True,
32+
)
33+
34+
return json.loads(result.stdout)
35+
36+
37+
def record_key(record: dict[str, Any]) -> tuple[str, str, str]:
38+
return (
39+
str(record.get("logged_at") or ""),
40+
str(record.get("entry") or ""),
41+
str(record.get("uuid") or ""),
42+
)
43+
44+
45+
def record_id(record: dict[str, Any]) -> str:
46+
uuid = record.get("uuid")
47+
48+
if uuid:
49+
return str(uuid)
50+
51+
return "|".join(record_key(record) + (str(record.get("description") or ""),))
52+
53+
54+
def record_time(record: dict[str, Any]) -> str:
55+
logged_time = record.get("logged_time")
56+
57+
if logged_time:
58+
return str(logged_time)
59+
60+
entry = str(record.get("entry") or "")
61+
62+
if not entry:
63+
return ""
64+
65+
try:
66+
return datetime.strptime(entry, "%Y%m%dT%H%M%SZ").replace(tzinfo=UTC).astimezone().strftime("%H:%M:%S")
67+
except ValueError:
68+
return ""
69+
70+
71+
def record_actor(record: dict[str, Any]) -> str:
72+
return " ".join(str(tag) for tag in record.get("tags", []) if tag != "journal")
73+
74+
75+
def record_kind(record: dict[str, Any]) -> str:
76+
return str(record.get("kind") or "")
77+
78+
79+
class Formatter:
80+
def __init__(self) -> None:
81+
self.actor_width = 0
82+
self.kind_width = 0
83+
84+
def observe(self, records: list[dict[str, Any]]) -> None:
85+
for record in records:
86+
self.actor_width = max(self.actor_width, len(record_actor(record)))
87+
self.kind_width = max(self.kind_width, len(record_kind(record)))
88+
89+
def format_record(self, record: dict[str, Any]) -> str:
90+
actor_width = self.actor_width + COLUMN_PADDING
91+
kind_width = self.kind_width + COLUMN_PADDING
92+
93+
return "{time} {actor:<{actor_width}} {kind:<{kind_width}} {description}".format(
94+
time=record_time(record),
95+
actor=record_actor(record),
96+
actor_width=actor_width,
97+
kind=record_kind(record),
98+
kind_width=kind_width,
99+
description=str(record.get("description") or ""),
100+
).rstrip()
101+
102+
103+
def print_records(records: list[dict[str, Any]], formatter: Formatter) -> None:
104+
for record in records:
105+
print(formatter.format_record(record), flush=True)
106+
107+
108+
def main() -> int:
109+
args = parse_args()
110+
records = sorted(load_records(), key=record_key)
111+
formatter = Formatter()
112+
formatter.observe(records)
113+
seen = {record_id(record) for record in records}
114+
115+
if args.lines > 0:
116+
print_records(records[-args.lines :], formatter)
117+
118+
while True:
119+
time.sleep(args.interval)
120+
121+
records = sorted(load_records(), key=record_key)
122+
formatter.observe(records)
123+
new_records = [record for record in records if record_id(record) not in seen]
124+
125+
for record in new_records:
126+
seen.add(record_id(record))
127+
128+
print_records(new_records, formatter)
129+
130+
131+
if __name__ == "__main__":
132+
try:
133+
raise SystemExit(main())
134+
except KeyboardInterrupt:
135+
print(file=sys.stderr)
136+
raise SystemExit(130)

0 commit comments

Comments
 (0)