Files
menologion/bin/horologion-waybar
T
funkpower 7beb53fc94 the documented way to use this did not work
`import qs.horologion.modules` was the README's headline instruction and it
fails outright: Quickshell resolves `qs` against the host config root, not
the subtree, so in an empty shell the type is unavailable and in a populated
one the widget would have silently bound to the host's Config and Theme
instead of its own. The imports inside are now relative and each directory
carries a qmldir, so `import "horologion/modules"` works in any host and the
three pieces really are usable on their own.

  - refresh() dropped a request made while a run was in flight, so a
    jurisdiction or date set a moment after startup never took effect until
    midnight. It queues now.
  - The waybar script exited non-zero on its error paths, and waybar hides a
    module whose script does that -- so a missing typikon made the module
    vanish rather than say so, and the .error CSS was dead. An unrecognised
    --format field raised instead of rendering empty, and the tooltip
    interpolated unescaped text into Pango markup, which matters because
    --update-menaion fetches commemorations from a page nobody here controls.
  - The installer aborted the whole run at the point where it printed
    'leaving it alone', and a wholly successful run exited 1 because the last
    statement was a failed [ -d ] test under set -e. --help demanded typikon,
    which is what --help would have told you to install.
  - eww painted every day with the feast colour and printed a dangling
    separator, because it compares against "" and eww renders a JSON null as
    the string "null". The parenthesised ?: form two lines away was already
    right. The week list also repeated today.

pip install typikon installed nothing; there is no such package, and that was
the first line of every install path. Pascha now carries its label instead of
being a bare ISO date in a corner.

tests/smoke.sh covers the scripts, which is where all of this lived and where
nothing was covered. It caught two of these while being written.
2026-09-18 10:29:37 -04:00

118 lines
4.1 KiB
Python
Executable File

#!/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"<b>{html.escape(head)}</b>")
if today.get("cycle"):
tip.append(f"<i>{html.escape(today['cycle'])}</i>")
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())