diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 4b8107175..fb2eb6322 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -14,6 +14,10 @@ not released yet * NEW event format option `status-symbol` which represents the status of an event with a symbol (e.g. `✓` for confirmed, `✗` for cancelled, `?` for tentative) +* NEW event format option `partstat-symbol` which represents the participation + status of an event with a symbol (e.g. `✓` for accepted, `✗` for declined, + `?` for tentative); partication status is shown for the email address + configured for the event's calendar 0.11.2 ====== diff --git a/doc/source/usage.rst b/doc/source/usage.rst index 9e5673f18..6e0add440 100644 --- a/doc/source/usage.rst +++ b/doc/source/usage.rst @@ -153,6 +153,9 @@ Several options are common to almost all of :program:`khal`'s commands status-symbol The status of the event as a symbol, `✓` or `✗` or `?`. + partstat-symbol + The participation status of the event as a symbol, `✓` or `✗` or `?`. + cancelled The string `CANCELLED` (plus one blank) if the event's status is cancelled, otherwise nothing. diff --git a/khal/cli.py b/khal/cli.py index 8b920f572..6a5231e60 100644 --- a/khal/cli.py +++ b/khal/cli.py @@ -164,6 +164,7 @@ def build_collection(conf, selection): 'color': cal['color'], 'priority': cal['priority'], 'ctype': cal['type'], + 'address': cal['address'], } collection = khalendar.CalendarCollection( calendars=props, diff --git a/khal/khalendar/event.py b/khal/khalendar/event.py index 87a9da9e6..be5a748b1 100644 --- a/khal/khalendar/event.py +++ b/khal/khalendar/event.py @@ -69,6 +69,7 @@ def __init__(self, color: Optional[str] = None, start: Optional[dt.datetime] = None, end: Optional[dt.datetime] = None, + address: Optional[str] = None, ): """ :param start: start datetime of this event instance @@ -88,6 +89,7 @@ def __init__(self, self.color = color self._start: dt.datetime self._end: dt.datetime + self.address = address if start is None: self._start = self._vevents[self.ref]['DTSTART'].dt @@ -291,6 +293,8 @@ def symbol_strings(self) -> Dict[str, str]: 'cancelled': '\N{Cross mark}', 'confirmed': '\N{Heavy check mark}', 'tentative': '\N{White question mark ornament}', + 'declined': '\N{Cross mark}', + 'accepted': '\N{Heavy check mark}', } else: return { @@ -303,6 +307,8 @@ def symbol_strings(self) -> Dict[str, str]: 'cancelled': 'X', 'confirmed': 'V', 'tentative': '?', + 'declined': 'X', + 'accepted': 'V', } @property @@ -574,6 +580,20 @@ def _status_str(self) -> str: statusstr = '' return statusstr + @property + def _partstat_str(self) -> str: + partstat = self.partstat + if partstat == 'ACCEPTED': + partstatstr = ' ' + self.symbol_strings['accepted'] + elif partstat == 'TENTATIVE': + partstatstr = ' ' + self.symbol_strings['tentative'] + elif partstat == 'DECLINED': + partstatstr = ' ' + self.symbol_strings['declined'] + else: + partstatstr = '' + return partstatstr + + def format(self, format_string: str, relative_to, env=None, colors: bool=True): """ @@ -702,6 +722,7 @@ def format(self, format_string: str, relative_to, env=None, colors: bool=True): attributes["repeat-pattern"] = self.recurpattern attributes["alarm-symbol"] = self._alarm_str attributes["status-symbol"] = self._status_str + attributes["partstat-symbol"] = self._partstat_str attributes["title"] = self.summary attributes["organizer"] = self.organizer.strip() attributes["description"] = self.description.strip() @@ -781,6 +802,13 @@ def delete_instance(self, instance: dt.datetime) -> None: def status(self) -> str: return self._vevents[self.ref].get('STATUS', '') + @property + def partstat(self) -> Optional[str]: + for attendee in self._vevents[self.ref].get('ATTENDEE', []): + print(attendee) + if attendee == 'mailto:' + self.address: + return attendee.params.get('PARTSTAT', '') + class DatetimeEvent(Event): pass diff --git a/khal/khalendar/khalendar.py b/khal/khalendar/khalendar.py index 5814c2592..0ec6a313e 100644 --- a/khal/khalendar/khalendar.py +++ b/khal/khalendar/khalendar.py @@ -284,6 +284,7 @@ def _construct_event(self, ref=ref, color=self._calendars[calendar]['color'], readonly=self._calendars[calendar]['readonly'], + address=self._calendars[calendar]['address'], ) return event diff --git a/tests/event_test.py b/tests/event_test.py index d5452e0c9..6369c4d10 100644 --- a/tests/event_test.py +++ b/tests/event_test.py @@ -673,3 +673,28 @@ def test_timezone_creation_with_arbitrary_dates(freeze_ts, event_time): assert len(vtimezone) > 14 assert 'BEGIN:STANDARD' in vtimezone assert 'BEGIN:DAYLIGHT' in vtimezone + + +def test_partstat(): + FORMAT_CALENDAR = ('{calendar-color}{partstat-symbol}{status-symbol}{start-end-time-style} ({calendar}) ' + '{title} [{location}]{repeat-symbol}') + + event = Event.fromString(_get_text('event_dt_partstat'), address='jdoe@example.com', **EVENT_KWARGS) + assert event.partstat == 'ACCEPTED' + assert event.format(FORMAT_CALENDAR, dt.date(2014, 4, 9)) == ' ✔09:30-10:30 (foobar) An Event []\x1b[0m' + + event = Event.fromString(_get_text('event_dt_partstat'), address='another@example.com', **EVENT_KWARGS) + assert event.partstat == 'DECLINED' + assert event.format(FORMAT_CALENDAR, dt.date(2014, 4, 9)) == ' ❌09:30-10:30 (foobar) An Event []\x1b[0m' + + event = Event.fromString(_get_text('event_dt_partstat'), address='jqpublic@example.com', **EVENT_KWARGS) + assert event.partstat == 'ACCEPTED' + assert event.format(FORMAT_CALENDAR, dt.date(2014, 4, 9)) == ' ✔09:30-10:30 (foobar) An Event []\x1b[0m' + +@pytest.mark.xfail +def test_partstat_deligated(): + event = Event.fromString(_get_text('event_dt_partstat'), address='hcabot@example.com', **EVENT_KWARGS) + assert event.partstat == 'ACCEPTED' + + event = Event.fromString(_get_text('event_dt_partstat'), address='iamboss@example.com', **EVENT_KWARGS) + assert event.partstat == 'ACCEPTED'