From f942ae90327836815da80aede826047f07b5f67a Mon Sep 17 00:00:00 2001 From: Dhruva Sambrani <44899822+DhruvaSambrani@users.noreply.github.com> Date: Fri, 17 Oct 2025 22:32:58 +0200 Subject: [PATCH 1/4] add alarms-list output formats --- khal/khalendar/event.py | 8 ++++++++ khal/utils.py | 6 ++++++ 2 files changed, 14 insertions(+) diff --git a/khal/khalendar/event.py b/khal/khalendar/event.py index d8ef9cc50..861810f6f 100644 --- a/khal/khalendar/event.py +++ b/khal/khalendar/event.py @@ -722,6 +722,14 @@ def attributes( attributes["repeat-symbol"] = self._recur_str attributes["repeat-pattern"] = self.recurpattern attributes["alarm-symbol"] = self._alarm_str + attributes["alarms-list"] = [ + { + "delta": alarm[0].total_seconds(), + "description": str(alarm[1]), + "delta-formatted": timedelta2str(alarm[0]) + } + for alarm in self.alarms + ] attributes["status-symbol"] = self._status_str attributes["partstat-symbol"] = self._partstat_str attributes["title"] = self.summary diff --git a/khal/utils.py b/khal/utils.py index fae58cdbe..bcc88c3fb 100644 --- a/khal/utils.py +++ b/khal/utils.py @@ -197,6 +197,11 @@ def fmt(rows): if 'calendar-color' in row: row['calendar-color'] = get_color(row['calendar-color']) + if 'alarms-list' in row and isinstance(row['alarms-list'], list): + row['alarms-list'] = ", ".join( + alarm['description']+"@"+alarm['delta-formatted'] for alarm in row['alarms-list'] + ) + s = format_string.format(**row) if colors: @@ -220,6 +225,7 @@ def fmt(rows): 'end-date-full', 'end-date-long-full', 'end-time-full', 'duration-full', 'start-style', 'end-style', 'to-style', 'start-end-time-style', 'end-necessary', 'end-necessary-long', 'repeat-symbol', 'repeat-pattern', + 'alarm-symbol', 'alarms-list', 'title', 'organizer', 'description', 'location', 'all-day', 'categories', 'uid', 'url', 'calendar', 'calendar-color', 'status', 'cancelled'] From d8fbe9add53f15e6ccf297e90ceba01f98c706ee Mon Sep 17 00:00:00 2001 From: Dhruva Sambrani <44899822+DhruvaSambrani@users.noreply.github.com> Date: Fri, 17 Oct 2025 23:37:45 +0200 Subject: [PATCH 2/4] add tests --- tests/cli_test.py | 25 +++++++++++++++++++++++++ tests/event_test.py | 21 +++++++++++++++++++++ 2 files changed, 46 insertions(+) diff --git a/tests/cli_test.py b/tests/cli_test.py index 5300a3207..882c61d08 100644 --- a/tests/cli_test.py +++ b/tests/cli_test.py @@ -475,6 +475,31 @@ def test_list_json(runner): assert result.output.startswith(expected) +def test_list_alarms(runner): + runner = runner(days=2) + now = dt.datetime.now().strftime('%d.%m.%Y') + runner.invoke( + main_khal, + f"new {now} 18:00 myevent --alarms -15m,1h".split()) + args = ['list', '--format', '{alarms-list}', '--day-format', ''] + result = runner.invoke(main_khal, args) + assert not result.exception + assert result.output.strip() == '@15m, @-1h' + + +def test_list_alarms_json(runner): + runner = runner() + now = dt.datetime.now().strftime('%d.%m.%Y') + runner.invoke( + main_khal, + f"new {now} 18:00 myevent --alarms 15m,1h".split()) + args = ['list', '--json', 'alarms-list'] + result = runner.invoke(main_khal, args) + expected = '[{"alarms-list": [{"delta": -900.0, "description": "", "delta-formatted": "-15m"}, {"delta": -3600.0, "description": "", "delta-formatted": "-1h"}]}]' + assert not result.exception + assert result.output.startswith(expected) + + def test_search(runner): runner = runner(days=2) now = dt.datetime.now().strftime('%d.%m.%Y') diff --git a/tests/event_test.py b/tests/event_test.py index 8255a76bf..d161e95bc 100644 --- a/tests/event_test.py +++ b/tests/event_test.py @@ -538,6 +538,27 @@ def test_event_alarm(): assert event.alarms == [(dt.timedelta(-1, 82800), vText('new event'))] +def test_event_alarm_list(): + """test the content of `alarms-list` attribute""" + event = Event.fromString(_get_text('event_dt_simple'), **EVENT_KWARGS) + assert event.alarms == [] + event.update_alarms([(dt.timedelta(minutes=30), 'alarm 1'), + (-dt.timedelta(hours=1, minutes=30), 'alarm 2')]) + attributes = event.attributes(dt.date.today()) + assert attributes['alarms-list'] == [ + {'delta': 1800.0, 'description': 'alarm 1', 'delta-formatted': '30m'}, + {'delta': -5400.0, 'description': 'alarm 2', 'delta-formatted': '-1h -30m'} + ] + + +def test_event_no_alarms_list(): + """test that `alarms-list` is empty for an event with no alarms""" + event = Event.fromString(_get_text('event_dt_simple'), **EVENT_KWARGS) + assert event.alarms == [] + attributes = event.attributes(dt.date.today()) + assert attributes['alarms-list'] == [] + + def test_event_attendees(): event = Event.fromString(_get_text('event_dt_simple'), **EVENT_KWARGS) assert event.attendees == "" From a882534e2b5e23a2860ab4080640f0f91143bd8c Mon Sep 17 00:00:00 2001 From: Dhruva Sambrani <44899822+DhruvaSambrani@users.noreply.github.com> Date: Fri, 17 Oct 2025 23:45:32 +0200 Subject: [PATCH 3/4] add docs and no alarm-symbol in json --- doc/source/usage.rst | 5 ++++- khal/utils.py | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/doc/source/usage.rst b/doc/source/usage.rst index c07ddf390..0d6ec29cb 100644 --- a/doc/source/usage.rst +++ b/doc/source/usage.rst @@ -115,6 +115,9 @@ Several options are common to almost all of :program:`khal`'s commands alarm-symbol An alarm symbol (alarm clock) if the event has at least one alarm. + alarms-list + A comma-separated list of alarms for the event (e.g., `alarm1@-15m, getready@-1h`). + location The event location. @@ -222,7 +225,7 @@ Several options are common to almost all of :program:`khal`'s commands end-date-long, end-time, start-full, start-long-full, start-date-full, start-date-long-full, start-time-full, end-full, end-long-full, end-date-full, end-date-long-full, - end-time-full, repeat-symbol, location, calendar, + end-time-full, repeat-symbol, alarms-list, location, calendar, calendar-color, start-style, to-style, end-style, start-end-time-style, end-necessary, end-necessary-long, status, cancelled, organizer, url, duration, duration-full, diff --git a/khal/utils.py b/khal/utils.py index bcc88c3fb..bb4f4e611 100644 --- a/khal/utils.py +++ b/khal/utils.py @@ -225,7 +225,7 @@ def fmt(rows): 'end-date-full', 'end-date-long-full', 'end-time-full', 'duration-full', 'start-style', 'end-style', 'to-style', 'start-end-time-style', 'end-necessary', 'end-necessary-long', 'repeat-symbol', 'repeat-pattern', - 'alarm-symbol', 'alarms-list', + 'alarms-list', 'title', 'organizer', 'description', 'location', 'all-day', 'categories', 'uid', 'url', 'calendar', 'calendar-color', 'status', 'cancelled'] From 54b6e63aea27a1eb66c6ab255cd71d4093b77a3b Mon Sep 17 00:00:00 2001 From: Dhruva Sambrani <44899822+DhruvaSambrani@users.noreply.github.com> Date: Mon, 20 Oct 2025 14:29:46 +0200 Subject: [PATCH 4/4] fix-precommit-ci attempt 1 --- khal/khalendar/event.py | 4 ++-- khal/utils.py | 3 ++- tests/cli_test.py | 5 ++++- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/khal/khalendar/event.py b/khal/khalendar/event.py index 861810f6f..c48cd4855 100644 --- a/khal/khalendar/event.py +++ b/khal/khalendar/event.py @@ -25,7 +25,7 @@ import datetime as dt import logging import os -from typing import Callable, Optional, Union +from typing import Any, Callable, Optional, Union import icalendar import icalendar.cal @@ -603,7 +603,7 @@ def attributes( """ env = env or {} - attributes = {} + attributes: dict[str, Any] = {} if isinstance(relative_to, tuple): relative_to_start, relative_to_end = relative_to else: diff --git a/khal/utils.py b/khal/utils.py index bb4f4e611..9b82628b5 100644 --- a/khal/utils.py +++ b/khal/utils.py @@ -199,7 +199,8 @@ def fmt(rows): if 'alarms-list' in row and isinstance(row['alarms-list'], list): row['alarms-list'] = ", ".join( - alarm['description']+"@"+alarm['delta-formatted'] for alarm in row['alarms-list'] + alarm['description']+"@"+alarm['delta-formatted'] + for alarm in row['alarms-list'] ) s = format_string.format(**row) diff --git a/tests/cli_test.py b/tests/cli_test.py index 882c61d08..bc43207c5 100644 --- a/tests/cli_test.py +++ b/tests/cli_test.py @@ -495,7 +495,10 @@ def test_list_alarms_json(runner): f"new {now} 18:00 myevent --alarms 15m,1h".split()) args = ['list', '--json', 'alarms-list'] result = runner.invoke(main_khal, args) - expected = '[{"alarms-list": [{"delta": -900.0, "description": "", "delta-formatted": "-15m"}, {"delta": -3600.0, "description": "", "delta-formatted": "-1h"}]}]' + expected = '[{"alarms-list": [\ + {"delta": -900.0, "description": "", "delta-formatted": "-15m"},\ + {"delta": -3600.0, "description": "", "delta-formatted": "-1h"}\ + ]}]' assert not result.exception assert result.output.startswith(expected)