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/khalendar/event.py b/khal/khalendar/event.py index d8ef9cc50..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: @@ -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..9b82628b5 100644 --- a/khal/utils.py +++ b/khal/utils.py @@ -197,6 +197,12 @@ 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 +226,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', + 'alarms-list', 'title', 'organizer', 'description', 'location', 'all-day', 'categories', 'uid', 'url', 'calendar', 'calendar-color', 'status', 'cancelled'] diff --git a/tests/cli_test.py b/tests/cli_test.py index 5300a3207..bc43207c5 100644 --- a/tests/cli_test.py +++ b/tests/cli_test.py @@ -475,6 +475,34 @@ 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 == ""