#!/usr/bin/env python3 """waybar custom module — the liturgical day in the bar. "custom/typikon": { "exec": "horologion-waybar", "return-type": "json", "interval": 1800, "on-click": "typikon --days 7" } The class is the fast level, so the bar can colour it: style `#custom-typikon.xerophagy` and the rest. `.obligatory` is added where a particular law actually binds. """ import argparse import collections import html import json import subprocess import sys def run(jurisdiction, days): cmd = ["typikon", "--json", "--days", str(days)] if jurisdiction: cmd += ["--jurisdiction", jurisdiction] out = subprocess.run(cmd, capture_output=True, text=True, check=True).stdout parsed = json.loads(out) return parsed.get("days", [parsed]) def main(): ap = argparse.ArgumentParser() ap.add_argument("-j", "--jurisdiction", default=None) ap.add_argument("-n", "--days", type=int, default=7) ap.add_argument("--no-emoji", action="store_true") ap.add_argument("--format", default="{emoji} {title}", help="{emoji} {title} {feast} {fast} {fastLabel} " "{season} {weekday} {churchDate}") args = ap.parse_args() try: days = run(args.jurisdiction, args.days) except FileNotFoundError: # Exit 0 on purpose, here and below: waybar hides a custom module whose # script exits non-zero, so a non-zero exit would make the module # vanish instead of showing why it could not answer. print(json.dumps({"text": "typikon?", "tooltip": "typikon is not on PATH", "class": "error"})) return 0 except (subprocess.CalledProcessError, ValueError) as exc: print(json.dumps({"text": "⚠", "tooltip": str(exc), "class": "error"})) return 0 today = days[0] # `or ""` would blank a legitimate 0, so only None and missing become "". fields = {k: ("" if v is None else v) for k, v in today.items()} if args.no_emoji: fields["emoji"] = "" try: # format_map over a defaultdict: a field name that does not exist comes # out empty rather than raising, because the --help text advertises # eight names and someone will always try a ninth. text = args.format.format_map(collections.defaultdict(str, fields)).strip() except (IndexError, ValueError) as exc: print(json.dumps({"text": "\u26a0", "class": "error", "tooltip": f"bad --format: {exc}"})) return 0 tip = [] head = today["title"] if today.get("churchDate") and today["churchDate"] not in head: head += f" ({today['churchDate']})" # waybar renders the tooltip as Pango markup, so anything interpolated into # it has to be escaped. Nothing in the shipped data contains an ampersand, # but `typikon --update-menaion` fetches commemorations from a third-party # page, and that is exactly where one arrives. tip.append(f"{html.escape(head)}") if today.get("cycle"): tip.append(f"{html.escape(today['cycle'])}") tip.append("") line = today["fastLabel"] if today.get("obligation") == "obligatory": line += " — obligatory" tip.append(line) if today.get("reason"): tip.append(today["reason"]) if today.get("season"): tip.append(today["season"]) for c in today.get("commemorations", []): if c != today["title"]: tip.append(f"· {c}") if len(days) > 1: tip.append("") for d in days[1:]: tip.append(f"{d['emoji']} {d['weekday'][:3]} {d['title'][:46]}") tip.append("") tip.append(f"Pascha {today['pascha']}") classes = [today["fast"]] if today.get("obligation") == "obligatory": classes.append("obligatory") if today.get("rank"): classes.append(today["rank"]) print(json.dumps({ "text": text, "tooltip": "\n".join(tip), "class": classes, "alt": today["fast"], }, ensure_ascii=False)) return 0 if __name__ == "__main__": sys.exit(main())