mirror of
https://github.com/kuhyx/screen-locker.git
synced 2026-11-14 00:50:51 +01:00
Fifth and last app onto freedays (~/utils/freedays). screen-locker is the one that already had this idea: scheduled_skips.json was a bare list of dates, checked first in the lock chain, that only this app could see. That is exactly what the shared pool generalises, so the chain now asks freedays and a day taken here also stands down diet-guard, wake-alarm, leetcode-guard and home-guard. Sick days are deliberately untouched. Their rolling windows (1/7d, 3/30d, 10/90d), the 120-character justification and the escalating countdown all exist to make a sick day *hard* to take. Free days are the opposite -- declared ahead, no reason, consecutive runs allowed. Merging the two policies would have had to ruin one of them, so only the storage of the frictionless half moved. This fixes a real bug in passing. is_scheduled_skip_today compared against a UTC day while add_scheduled_skip.py wrote a local one, so a skip added near midnight could name a date the chain never matched. freedays owns "what is today" for both sides now, in local time, and the disagreement cannot recur. scripts/migrate_scheduled_skips.py moves the existing dates across; it is idempotent and has a --dry-run. Already run here: the 8 real skips (2026-05-19/20/21, 05-29, 05-30, 08-17, 08-22, 08-29) are in the pool and count against 2026, leaving 27 of 35. The original file is gitignored and was left on disk untouched. add_scheduled_skip.py becomes a thin wrapper over freedays rather than being deleted, because arm.sh calls it and the name is in muscle memory. Left as-is it would have kept writing to a file nothing reads. conftest.py went over the 250-line cap, so the two fixtures that keep the suite off real user state moved to _real_state_fixtures.py. They belong together and neither subsumes the other: _isolate_home covers paths resolved from Path.home() at call time, while the freedays redirect covers paths bound into module constants at import time, which redirecting home afterwards cannot reach. requirements.txt also takes pytest-randomly 5.0.0, ruff 0.16.6 and types-requests 2.33.0.20260906, and the pylint hooks' gatelock/crdt-sync pins move off v0.7.1/v0.9.0 -- pip refuses two direct URLs for one distribution, so those stale pins would have made the hook environments unresolvable rather than merely old. 1256 tests, 100% branch coverage. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
83 lines
2.5 KiB
Python
Executable File
83 lines
2.5 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""Mark a day free, for this app and every other gate app at once.
|
|
|
|
This used to append to ``screen_locker/scheduled_skips.json``, which only
|
|
screen-locker read. That file is no longer consulted by anything: the lock
|
|
chain now asks the shared pool (``~/utils/freedays``), so a day taken here
|
|
also stands down diet-guard, wake-alarm, leetcode-guard and home-guard.
|
|
|
|
Kept as a thin wrapper rather than deleted because ``arm.sh`` calls it, and
|
|
because this is the name already in muscle memory. ``freedays mark`` does
|
|
exactly the same thing.
|
|
|
|
scripts/add_scheduled_skip.py --date today
|
|
scripts/add_scheduled_skip.py --date 2026-12-24
|
|
|
|
The date is **local**. The old version computed a local "today" while the
|
|
lock chain compared against a UTC one, so a skip added near midnight could
|
|
name a date the chain never matched. freedays owns "what is today" for both
|
|
sides now, so that disagreement cannot recur.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import logging
|
|
import sys
|
|
|
|
import freedays
|
|
|
|
_logger = logging.getLogger(__name__)
|
|
|
|
|
|
def _report(message: str) -> None:
|
|
sys.stdout.write(f"{message}\n")
|
|
|
|
|
|
def main(argv: list[str] | None = None) -> int:
|
|
"""Mark the requested day free in the shared pool.
|
|
|
|
Returns:
|
|
``0`` when the day is free (including when it already was), ``1``
|
|
when the pool refused, ``2`` when the date could not be parsed.
|
|
"""
|
|
parser = argparse.ArgumentParser(description=__doc__.splitlines()[0])
|
|
parser.add_argument(
|
|
"--date",
|
|
default="today",
|
|
help="YYYY-MM-DD, or 'today'/'tomorrow' (default: today)",
|
|
)
|
|
parser.add_argument(
|
|
"--reason",
|
|
default="",
|
|
help="optional free text; never required",
|
|
)
|
|
args = parser.parse_args(argv)
|
|
|
|
try:
|
|
day = freedays.resolve(args.date)
|
|
except ValueError as exc:
|
|
_logger.warning("could not parse the requested date: %s", exc)
|
|
_report(f"error: {exc}")
|
|
return 2
|
|
|
|
iso = freedays.to_iso(day)
|
|
if freedays.is_free_day(day):
|
|
_report(f"{iso} is already a free day; nothing to do")
|
|
return 0
|
|
|
|
try:
|
|
freedays.mark(day, reason=args.reason)
|
|
except freedays.FreeDayError as exc:
|
|
_logger.warning("the pool refused %s: %s", iso, exc)
|
|
_report(f"error: {exc}")
|
|
return 1
|
|
|
|
left = freedays.status(year=day.year).left
|
|
_report(f"{iso} is now a free day for every gate app ({left} left in {day.year})")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|