Skip to content

Commit

Permalink
Merge pull request #1271 from pimutils/feature/status
Browse files Browse the repository at this point in the history
Better support for status and initial support for partstat
  • Loading branch information
geier authored Nov 15, 2023
2 parents ffef884 + 765bae2 commit 214886d
Show file tree
Hide file tree
Showing 13 changed files with 162 additions and 10 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,13 @@ not released yet
* NEW Add default alarms configuration option
* FIX defaults for `default_event_duration` and `default_dayevent_duration`
where mixed up, `default_dayevent_duration` is the default for all-day events
* 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 addresses
configured for the event's calendar

0.11.2
======
Expand Down
6 changes: 6 additions & 0 deletions doc/source/usage.rst
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,12 @@ Several options are common to almost all of :program:`khal`'s commands
The status of the event (if this event has one), something like
`CONFIRMED` or `CANCELLED`.

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.
Expand Down
1 change: 1 addition & 0 deletions khal/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,7 @@ def build_collection(conf, selection):
'color': cal['color'],
'priority': cal['priority'],
'ctype': cal['type'],
'addresses': cal['addresses'],
}
collection = khalendar.CalendarCollection(
calendars=props,
Expand Down
1 change: 1 addition & 0 deletions khal/custom_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ class CalendarConfiguration(TypedDict):
color: str
priority: int
ctype: str
addresses: str


class LocaleConfiguration(TypedDict):
Expand Down
53 changes: 50 additions & 3 deletions khal/khalendar/event.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,8 @@ def __init__(self,
color: Optional[str] = None,
start: Optional[dt.datetime] = None,
end: Optional[dt.datetime] = None,
) -> None:
addresses: Optional[List[str]] =None,
):
"""
:param start: start datetime of this event instance
:param end: end datetime of this event instance
Expand All @@ -87,6 +88,7 @@ def __init__(self,
self.color = color
self._start: dt.datetime
self._end: dt.datetime
self.addresses = addresses if addresses else []

if start is None:
self._start = self._vevents[self.ref]['DTSTART'].dt
Expand Down Expand Up @@ -286,7 +288,12 @@ def symbol_strings(self) -> Dict[str, str]:
'range': '\N{Left right arrow}',
'range_end': '\N{Rightwards arrow to bar}',
'range_start': '\N{Rightwards arrow from bar}',
'right_arrow': '\N{Rightwards arrow}'
'right_arrow': '\N{Rightwards arrow}',
'cancelled': '\N{Cross mark}',
'confirmed': '\N{Heavy check mark}',
'tentative': '?',
'declined': '\N{Cross mark}',
'accepted': '\N{Heavy check mark}',
}
else:
return {
Expand All @@ -295,7 +302,12 @@ def symbol_strings(self) -> Dict[str, str]:
'range': '<->',
'range_end': '->|',
'range_start': '|->',
'right_arrow': '->'
'right_arrow': '->',
'cancelled': 'X',
'confirmed': 'V',
'tentative': '?',
'declined': 'X',
'accepted': 'V',
}

@property
Expand Down Expand Up @@ -554,6 +566,31 @@ def _alarm_str(self) -> str:
alarmstr = ''
return alarmstr

@property
def _status_str(self) -> str:
if self.status == 'CANCELLED':
statusstr = self.symbol_strings['cancelled']
elif self.status == 'TENTATIVE':
statusstr = self.symbol_strings['tentative']
elif self.status == 'CONFIRMED':
statusstr = self.symbol_strings['confirmed']
else:
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 attributes(
self,
relative_to: Union[Tuple[dt.date, dt.date], dt.date],
Expand Down Expand Up @@ -684,6 +721,8 @@ def attributes(
attributes["repeat-symbol"] = self._recur_str
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()
Expand Down Expand Up @@ -766,6 +805,14 @@ 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', []):
for address in self.addresses:
if attendee == 'mailto:' + address:
return attendee.params.get('PARTSTAT', '')
return None


class DatetimeEvent(Event):
pass
Expand Down
1 change: 1 addition & 0 deletions khal/khalendar/khalendar.py
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,7 @@ def _construct_event(self,
ref=ref,
color=self._calendars[calendar]['color'],
readonly=self._calendars[calendar]['readonly'],
addresses=self._calendars[calendar]['addresses'],
)
return event

Expand Down
5 changes: 5 additions & 0 deletions khal/settings/khal.spec
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,11 @@ readonly = boolean(default=False)
# *calendars* subsection will be used.
type = option('calendar', 'birthdays', 'discover', default='calendar')

# All email addresses associated with this account, separated by commas.
# For now it is only used to check what participation status ("PARTSTAT")
# belongs to the user.
addresses = force_list(default='')

[sqlite]
# khal stores its internal caching database here, by default this will be in the *$XDG_DATA_HOME/khal/khal.db* (this will most likely be *~/.local/share/khal/khal.db*).
path = expand_db_path(default=None)
Expand Down
1 change: 1 addition & 0 deletions tests/configs/small.conf
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@
[[work]]
path = ~/.calendars/work/
readonly = True
addresses = [email protected]
4 changes: 3 additions & 1 deletion tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ def coll_vdirs(tmpdir) -> CollVdirType:
color='dark blue',
priority=10,
ctype='calendar',
addresses='[email protected]',
)
vdirs[name] = Vdir(path, '.ics')
coll = CalendarCollection(calendars=calendars, dbpath=':memory:', locale=LOCALE_BERLIN)
Expand All @@ -71,7 +72,8 @@ def coll_vdirs_birthday(tmpdir):
os.makedirs(path, mode=0o770)
readonly = True if name == 'a_calendar' else False
calendars[name] = {'name': name, 'path': path, 'color': 'dark blue',
'readonly': readonly, 'unicode_symbols': True, 'ctype': 'birthdays'}
'readonly': readonly, 'unicode_symbols': True, 'ctype': 'birthdays',
'addresses': '[email protected]'}
vdirs[name] = Vdir(path, '.vcf')
coll = CalendarCollection(calendars=calendars, dbpath=':memory:', locale=LOCALE_BERLIN)
coll.default_calendar_name = cal1
Expand Down
43 changes: 43 additions & 0 deletions tests/event_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,15 @@ def test_event_rd():
assert event.recurring is True


def test_status_confirmed():
event = Event.fromString(_get_text('event_dt_status_confirmed'), **EVENT_KWARGS)
assert event.status == 'CONFIRMED'
FORMAT_CALENDAR = ('{calendar-color}{status-symbol}{start-end-time-style} ({calendar}) '
'{title} [{location}]{repeat-symbol}')

assert human_formatter(FORMAT_CALENDAR)(event.attributes(dt.date(2014, 4, 9))) == \
'✔09:30-10:30 (foobar) An Event []\x1b[0m'

def test_event_d_long():
event_d_long = _get_text('event_d_long')
event = Event.fromString(event_d_long, **EVENT_KWARGS)
Expand Down Expand Up @@ -686,3 +695,37 @@ def test_parameters_description():
assert event.description == (
'Hey, \n\nJust setting aside some dedicated time to talk about redacted.'
)

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'), addresses=['[email protected]'], **EVENT_KWARGS)
assert event.partstat == 'ACCEPTED'
assert human_formatter(FORMAT_CALENDAR)(event.attributes(dt.date(2014, 4, 9))) == \
'✔09:30-10:30 (foobar) An Event []\x1b[0m'

event = Event.fromString(
_get_text('event_dt_partstat'), addresses=['[email protected]'], **EVENT_KWARGS)
assert event.partstat == 'DECLINED'
assert human_formatter(FORMAT_CALENDAR)(event.attributes(dt.date(2014, 4, 9))) == \
'❌09:30-10:30 (foobar) An Event []\x1b[0m'

event = Event.fromString(
_get_text('event_dt_partstat'), addresses=['[email protected]'], **EVENT_KWARGS)
assert event.partstat == 'ACCEPTED'
assert human_formatter(FORMAT_CALENDAR)(event.attributes(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'), addresses=['[email protected]'], **EVENT_KWARGS)
assert event.partstat == 'ACCEPTED'

event = Event.fromString(
_get_text('event_dt_partstat'), addresses=['[email protected]'], **EVENT_KWARGS)
assert event.partstat == 'ACCEPTED'
22 changes: 22 additions & 0 deletions tests/ics/event_dt_partstat.ics
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//PIMUTILS.ORG//NONSGML khal / icalendar //EN
BEGIN:VEVENT
SUMMARY:An Event
DTSTART;TZID=Europe/Berlin:20140409T093000
DTEND;TZID=Europe/Berlin:20140409T103000
DTSTAMP:20140401T234817Z
UID:V042MJ8B3SJNFXQOJL6P53OFMHJE8Z3VZWOU
ATTENDEE;ROLE=REQ-PARTICIPANT;PARTSTAT=TENTATIVE;DELEGATED-FROM=
"mailto:[email protected]";CN=Henry Cabot:mailto:hcabot@
example.com
ATTENDEE;ROLE=NON-PARTICIPANT;PARTSTAT=DELEGATED;DELEGATED-TO=
"mailto:[email protected]";CN=The Big Cheese:mailto:iamboss
@example.com
ATTENDEE;ROLE=REQ-PARTICIPANT;PARTSTAT=ACCEPTED;CN=Jane Doe
:mailto:[email protected]
ATTENDEE;PARTSTAT=ACCEPTED:mailto:[email protected]
ATTENDEE;PARTSTAT=DECLINED:mailto:[email protected]
ATTENDEE;PARTSTAT=TENTATIVE:mailto:[email protected]
END:VEVENT
END:VCALENDAR
12 changes: 12 additions & 0 deletions tests/ics/event_dt_status_confirmed.ics
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//PIMUTILS.ORG//NONSGML khal / icalendar //EN
BEGIN:VEVENT
SUMMARY:An Event
DTSTART;TZID=Europe/Berlin:20140409T093000
DTEND;TZID=Europe/Berlin:20140409T103000
DTSTAMP:20140401T234817Z
UID:V042MJ8B3SJNFXQOJL6P53OFMHJE8Z3VZWOU
STATUS:CONFIRMED
END:VEVENT
END:VCALENDAR
16 changes: 10 additions & 6 deletions tests/settings_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,14 @@ def test_simple_config(self):
)
comp_config = {
'calendars': {
'home': {'path': os.path.expanduser('~/.calendars/home/'),
'readonly': False, 'color': None, 'priority': 10, 'type': 'calendar'},
'work': {'path': os.path.expanduser('~/.calendars/work/'),
'readonly': False, 'color': None, 'priority': 10, 'type': 'calendar'},
'home': {
'path': os.path.expanduser('~/.calendars/home/'), 'readonly': False,
'color': None, 'priority': 10, 'type': 'calendar', 'addresses': [''],
},
'work': {
'path': os.path.expanduser('~/.calendars/work/'), 'readonly': False,
'color': None, 'priority': 10, 'type': 'calendar', 'addresses': [''],
},
},
'sqlite': {'path': os.path.expanduser('~/.local/share/khal/khal.db')},
'locale': LOCALE_BERLIN,
Expand Down Expand Up @@ -81,10 +85,10 @@ def test_small(self):
'calendars': {
'home': {'path': os.path.expanduser('~/.calendars/home/'),
'color': 'dark green', 'readonly': False, 'priority': 20,
'type': 'calendar'},
'type': 'calendar', 'addresses': ['']},
'work': {'path': os.path.expanduser('~/.calendars/work/'),
'readonly': True, 'color': None, 'priority': 10,
'type': 'calendar'}},
'type': 'calendar', 'addresses': ['[email protected]']}},
'sqlite': {'path': os.path.expanduser('~/.local/share/khal/khal.db')},
'locale': {
'local_timezone': get_localzone(),
Expand Down

0 comments on commit 214886d

Please sign in to comment.