Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package python-khal for openSUSE:Factory checked in at 2026-08-22 21:35:38 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-khal (Old) and /work/SRC/openSUSE:Factory/.python-khal.new.1258 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-khal" Sat Aug 22 21:35:38 2026 rev:10 rq:1373022 version:0.14.1 Changes: -------- --- /work/SRC/openSUSE:Factory/python-khal/python-khal.changes 2026-03-30 18:32:16.845520085 +0200 +++ /work/SRC/openSUSE:Factory/.python-khal.new.1258/python-khal.changes 2026-08-22 21:37:46.538720427 +0200 @@ -1,0 +2,8 @@ +Sat Aug 22 09:11:37 UTC 2026 - Dirk Müller <[email protected]> + +- update to 0.14.1: + * FIX add timezone when assigning start/end time + * FIX crash on urwid >= 4.0.5 + * NEW add alarms-list output formats + +------------------------------------------------------------------- Old: ---- khal-0.14.0.tar.gz New: ---- khal-0.14.1.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-khal.spec ++++++ --- /var/tmp/diff_new_pack.olRC46/_old 2026-08-22 21:37:47.112740946 +0200 +++ /var/tmp/diff_new_pack.olRC46/_new 2026-08-22 21:37:47.113740982 +0200 @@ -18,7 +18,7 @@ %{?sle15_python_module_pythons} Name: python-khal -Version: 0.14.0 +Version: 0.14.1 Release: 0 Summary: CLI calendar with CalDAV support License: MIT ++++++ khal-0.14.0.tar.gz -> khal-0.14.1.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/khal-0.14.0/CHANGELOG.rst new/khal-0.14.1/CHANGELOG.rst --- old/khal-0.14.0/CHANGELOG.rst 2026-03-24 17:05:07.000000000 +0100 +++ new/khal-0.14.1/CHANGELOG.rst 2026-08-21 08:25:24.936279000 +0200 @@ -7,6 +7,13 @@ may want to subscribe to `GitHub's tag feed <https://github.com/geier/khal/tags.atom>`_. +0.14.1 +====== +2026-08-21 +* FIX add timezone when assigning start/end time +* FIX crash on urwid >= 4.0.5 +* NEW add alarms-list output formats + 0.14.0 ====== 2026-03-24 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/khal-0.14.0/PKG-INFO new/khal-0.14.1/PKG-INFO --- old/khal-0.14.0/PKG-INFO 2026-03-24 17:05:08.431989200 +0100 +++ new/khal-0.14.1/PKG-INFO 2026-08-21 08:25:26.511973100 +0200 @@ -1,6 +1,6 @@ Metadata-Version: 2.4 Name: khal -Version: 0.14.0 +Version: 0.14.1 Summary: Standards based terminal calendar Author-email: khal contributors <[email protected]> License-Expression: MIT @@ -61,7 +61,7 @@ *Khal* is a standards based CLI and terminal calendar program, able to synchronize with CalDAV_ servers through vdirsyncer_. -.. image:: http://lostpackets.de/images/khal.png +.. image:: https://lostpackets.de/images/khal.png Features -------- @@ -86,16 +86,16 @@ .. _vdir: https://vdirsyncer.readthedocs.org/en/stable/vdir.html .. _vdirsyncer: https://github.com/pimutils/vdirsyncer -.. _CalDAV: http://en.wikipedia.org/wiki/CalDAV +.. _CalDAV: https://en.wikipedia.org/wiki/CalDAV .. _github: https://github.com/pimutils/khal/ -.. __: http://en.wikipedia.org/wiki/Comparison_of_CalDAV_and_CardDAV_implementations +.. __: https://en.wikipedia.org/wiki/Comparison_of_CalDAV_and_CardDAV_implementations Documentation ------------- For khal's documentation have a look at readthedocs_. -.. _readthedocs: http://khal.readthedocs.org/ +.. _readthedocs: https://khal.readthedocs.org/ Installation ------------ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/khal-0.14.0/README.rst new/khal-0.14.1/README.rst --- old/khal-0.14.0/README.rst 2026-03-24 17:05:07.000000000 +0100 +++ new/khal-0.14.1/README.rst 2026-08-21 08:25:24.936279000 +0200 @@ -12,7 +12,7 @@ *Khal* is a standards based CLI and terminal calendar program, able to synchronize with CalDAV_ servers through vdirsyncer_. -.. image:: http://lostpackets.de/images/khal.png +.. image:: https://lostpackets.de/images/khal.png Features -------- @@ -37,16 +37,16 @@ .. _vdir: https://vdirsyncer.readthedocs.org/en/stable/vdir.html .. _vdirsyncer: https://github.com/pimutils/vdirsyncer -.. _CalDAV: http://en.wikipedia.org/wiki/CalDAV +.. _CalDAV: https://en.wikipedia.org/wiki/CalDAV .. _github: https://github.com/pimutils/khal/ -.. __: http://en.wikipedia.org/wiki/Comparison_of_CalDAV_and_CardDAV_implementations +.. __: https://en.wikipedia.org/wiki/Comparison_of_CalDAV_and_CardDAV_implementations Documentation ------------- For khal's documentation have a look at readthedocs_. -.. _readthedocs: http://khal.readthedocs.org/ +.. _readthedocs: https://khal.readthedocs.org/ Installation ------------ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/khal-0.14.0/doc/source/conf.py new/khal-0.14.1/doc/source/conf.py --- old/khal-0.14.0/doc/source/conf.py 2026-03-24 17:05:07.000000000 +0100 +++ new/khal-0.14.1/doc/source/conf.py 2026-08-21 08:25:24.937338400 +0200 @@ -17,7 +17,7 @@ try: # Available from configobj 5.1.0 - import configobj.validate as validate + from configobj import validate except ModuleNotFoundError: import validate @@ -38,7 +38,7 @@ def write_section(specsection, secname, key, comment, output): # why is _parse_check a "private" method? seems to be rather useful... # we don't need fun_kwargs - fun_name, fun_args, fun_kwargs, default = validator._parse_check(specsection) + fun_name, fun_args, _fun_kwargs, default = validator._parse_check(specsection) output.write(f"\n.. _{secname}-{key}:") output.write("\n") output.write(f"\n.. object:: {key}\n") diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/khal-0.14.0/doc/source/usage.rst new/khal-0.14.1/doc/source/usage.rst --- old/khal-0.14.0/doc/source/usage.rst 2026-03-24 17:05:07.000000000 +0100 +++ new/khal-0.14.1/doc/source/usage.rst 2026-08-21 08:25:24.938294600 +0200 @@ -115,6 +115,9 @@ 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 @@ 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 -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/khal-0.14.0/khal/controllers.py new/khal-0.14.1/khal/controllers.py --- old/khal-0.14.0/khal/controllers.py 2026-03-24 17:05:07.000000000 +0100 +++ new/khal-0.14.1/khal/controllers.py 2026-08-21 08:25:24.939682500 +0200 @@ -163,7 +163,7 @@ start = dt.datetime(*dt.date.today().timetuple()[:3]) end = start + default_timedelta_date else: - start, end, allday = parse_datetime.guessrangefstr( + start, end, _allday = parse_datetime.guessrangefstr( daterange, locale, default_timedelta_date=default_timedelta_date, @@ -580,6 +580,9 @@ value = prompt("datetime range", default=current) try: start, end, allday = parse_datetime.guessrangefstr(ansi.sub("", value), locale) + if not allday: + start = locale["local_timezone"].localize(start) + end = locale["local_timezone"].localize(end) event.update_start_end(start, end) edited = True except Exception: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/khal-0.14.0/khal/exceptions.py new/khal-0.14.1/khal/exceptions.py --- old/khal-0.14.0/khal/exceptions.py 2026-03-24 17:05:07.000000000 +0100 +++ new/khal-0.14.1/khal/exceptions.py 2026-08-21 08:25:24.939682500 +0200 @@ -23,14 +23,10 @@ class Error(Exception): """base class for all of khal's Exceptions""" - pass - class FatalError(Error): """execution cannot continue""" - pass - class DateTimeParseError(FatalError): pass @@ -43,14 +39,10 @@ class UnsupportedFeatureError(Error): """something Failed but we know why""" - pass - class UnsupportedRecurrence(Error): """raised if the RRULE is not understood by dateutil.rrule""" - pass - class InvalidDate(Error): pass diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/khal-0.14.0/khal/icalendar.py new/khal-0.14.1/khal/icalendar.py --- old/khal-0.14.0/khal/icalendar.py 2026-03-24 17:05:07.000000000 +0100 +++ new/khal-0.14.1/khal/icalendar.py 2026-08-21 08:25:24.940310000 +0200 @@ -79,7 +79,7 @@ try: ics = ics_from_list(events, tzs, random_uid, default_timezone) except Exception as exception: - logger.warn(f"Error when trying to import the event {uid}") + logger.warning(f"Error when trying to import the event {uid}") saved_exception = exception else: out.append(ics) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/khal-0.14.0/khal/khalendar/backend.py new/khal-0.14.1/khal/khalendar/backend.py --- old/khal-0.14.0/khal/khalendar/backend.py 2026-03-24 17:05:07.000000000 +0100 +++ new/khal-0.14.1/khal/khalendar/backend.py 2026-08-21 08:25:24.940524600 +0200 @@ -98,7 +98,7 @@ self._at_once = True try: yield self - except: + except Exception: raise else: self.conn.commit() @@ -277,7 +277,7 @@ self.deletelike(href + "%", calendar=calendar) ical = cal_from_ics(vevent_str) vcard = ical.walk()[0] - for key in vcard.keys(): + for key in vcard: if key in ["BDAY", "X-ANNIVERSARY", "ANNIVERSARY"] or key.endswith("X-ABDATE"): date = vcard[key] uuid = vcard.get("UID") @@ -589,7 +589,7 @@ """returns the ical string matching href and calendar""" assert calendar is not None sql_s = "SELECT item, etag FROM events WHERE href = ? AND calendar = ?;" - item, etag = self.sql_ex(sql_s, (href, calendar))[0] + item, _etag = self.sql_ex(sql_s, (href, calendar))[0] return item def get_with_etag(self, href: str, calendar: str) -> tuple[str, str]: @@ -694,11 +694,11 @@ return "anniversary" elif key.endswith("X-ABDATE"): desc_key = key[:-8] + "X-ABLABEL" - if desc_key in vcard.keys(): + if desc_key in vcard: return vcard[desc_key] else: desc_key = key[:-8] + "X-ABLabel" - if desc_key in vcard.keys(): + if desc_key in vcard: return vcard[desc_key] else: return "custom event from vcard" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/khal-0.14.0/khal/khalendar/event.py new/khal-0.14.1/khal/khalendar/event.py --- old/khal-0.14.0/khal/khalendar/event.py 2026-03-24 17:05:07.000000000 +0100 +++ new/khal-0.14.1/khal/khalendar/event.py 2026-08-21 08:25:24.940524600 +0200 @@ -26,6 +26,7 @@ import logging import os from collections.abc import Callable +from typing import Any import icalendar import icalendar.cal @@ -151,7 +152,7 @@ vevents["PROTO"] = event if ref is None: - ref = "PROTO" if ref in vevents.keys() else list(vevents.keys())[0] + ref = "PROTO" if ref in vevents else list(vevents.keys())[0] try: if type(vevents[ref]["DTSTART"].dt) is not type(vevents[ref]["DTEND"].dt): raise ValueError("DTSTART and DTEND should be of the same type (datetime or date)") @@ -612,7 +613,7 @@ """ env = env or {} - attributes = {} + attributes: dict[str, Any] = {} if isinstance(relative_to, tuple): relative_to_start, relative_to_end = relative_to else: @@ -734,6 +735,14 @@ 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 -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/khal-0.14.0/khal/parse_datetime.py new/khal-0.14.1/khal/parse_datetime.py --- old/khal-0.14.0/khal/parse_datetime.py 2026-03-24 17:05:07.000000000 +0100 +++ new/khal-0.14.1/khal/parse_datetime.py 2026-08-21 08:25:24.940524600 +0200 @@ -92,8 +92,8 @@ for _ in range(parts): item = dtime_list.pop(0) if " " in item: - logger.warn("detected a space in datetime specification, this can lead to errors.") - logger.warn("Make sure not to quote your datetime specification.") + logger.warning("detected a space in datetime specification, this can lead to errors.") + logger.warning("Make sure not to quote your datetime specification.") if infer_year: dtstart = dt.datetime(*(default_day.timetuple()[:1] + dtstart_struct[1:5])) @@ -242,8 +242,7 @@ (datetimefwords, "", False, False), ]: # if a `short` format contains a year, treat it as a `long` format - if infer_year and "97" in dt.datetime(1997, 10, 11).strftime(dtformat): - infer_year = False + infer_year = infer_year and "97" not in dt.datetime(1997, 10, 11).strftime(dtformat) try: dtstart = fun(dtime_list, dtformat, infer_year=infer_year) except (ValueError, DateTimeParseError): @@ -389,7 +388,7 @@ end = start + delta except (ValueError, DateTimeParseError): split = endstr.split(" ") - end, end_allday = guessdatetimefstr( + end, _end_allday = guessdatetimefstr( split, locale, default_day=start.date(), in_future=False ) if len(split) != 0: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/khal-0.14.0/khal/settings/exceptions.py new/khal-0.14.1/khal/settings/exceptions.py --- old/khal-0.14.0/khal/settings/exceptions.py 2026-03-24 17:05:07.000000000 +0100 +++ new/khal-0.14.1/khal/settings/exceptions.py 2026-08-21 08:25:24.941382000 +0200 @@ -25,8 +25,6 @@ class InvalidSettingsError(Error): """Invalid Settings detected""" - pass - class CannotParseConfigFileError(InvalidSettingsError): pass diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/khal-0.14.0/khal/settings/utils.py new/khal-0.14.1/khal/settings/utils.py --- old/khal-0.14.0/khal/settings/utils.py 2026-03-24 17:05:07.000000000 +0100 +++ new/khal-0.14.1/khal/settings/utils.py 2026-08-21 08:25:24.941382000 +0200 @@ -71,7 +71,7 @@ raise VdtValueError(f"Invalid timedelta: {string}") -def weeknumber_option(option: str) -> Literal["left", "right"] | Literal[False]: +def weeknumber_option(option: str) -> Literal["left", "right", False]: """checks if *option* is a valid value :param option: the option the user set in the config file @@ -133,7 +133,7 @@ # 5) an HTML-style color code if ( color in ["", "auto"] - or color in COLORS.keys() + or color in COLORS or (color.isdigit() and int(color) >= 0 and int(color) <= 255) or ( color.startswith("#") diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/khal-0.14.0/khal/ui/__init__.py new/khal-0.14.1/khal/ui/__init__.py --- old/khal-0.14.0/khal/ui/__init__.py 2026-03-24 17:05:07.000000000 +0100 +++ new/khal-0.14.1/khal/ui/__init__.py 2026-08-21 08:25:24.941382000 +0200 @@ -1303,17 +1303,17 @@ colors = {} # Colorcube colorlevels = (0x00, 0x5F, 0x87, 0xAF, 0xD7, 0xFF) - for r in range(0, 6): - for g in range(0, 6): - for b in range(0, 6): + for r in range(6): + for g in range(6): + for b in range(6): colors[r * 36 + g * 6 + b + 16] = ( colorlevels[r], colorlevels[g], colorlevels[b], ) # Grayscale - graylevels = [0x08 + 10 * i for i in range(0, 24)] - for c in range(0, 24): + graylevels = [0x08 + 10 * i for i in range(24)] + for c in range(24): colors[232 + c] = (graylevels[c],) * 3 # Parse the HTML-style color into the variables r, g, b. if len(color) == 4: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/khal-0.14.0/khal/ui/base.py new/khal-0.14.1/khal/ui/base.py --- old/khal-0.14.0/khal/ui/base.py 2026-03-24 17:05:07.000000000 +0100 +++ new/khal-0.14.1/khal/ui/base.py 2026-08-21 08:25:24.942063800 +0200 @@ -201,7 +201,7 @@ this callback is called with the given data (if any) before the previous pane gets redrawn. """ - old_pane, cb = self._track.pop() + _old_pane, cb = self._track.pop() if cb: cb(data) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/khal-0.14.0/khal/ui/calendarwidget.py new/khal-0.14.1/khal/ui/calendarwidget.py --- old/khal-0.14.0/khal/ui/calendarwidget.py 2026-03-24 17:05:07.000000000 +0100 +++ new/khal-0.14.1/khal/ui/calendarwidget.py 2026-08-21 08:25:24.942063800 +0200 @@ -266,7 +266,7 @@ return super().render(size, focus) def mouse_event(self, *args): - size, event, button, col, row, focus = args + _size, event, button, _col, _row, _focus = args if event == "mouse press" and button == 1: self.focus.focus.set_styles(self.focus.get_styles(self.body.focus_date, False)) @@ -451,8 +451,8 @@ def reset_styles_range(self, min_date: dt.date, max_date: dt.date) -> None: """reset styles for all (displayed) dates between min_date and max_date""" - minr, minc = self.get_date_pos(max(min_date, self.earliest_date)) - maxr, maxc = self.get_date_pos(min(max_date, self.latest_date)) + minr, _minc = self.get_date_pos(max(min_date, self.earliest_date)) + maxr, _maxc = self.get_date_pos(min(max_date, self.latest_date)) focus_pos = self.focus, self[self.focus].focus_col for row in range(minr, maxr + 1): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/khal-0.14.0/khal/ui/widgets.py new/khal-0.14.1/khal/ui/widgets.py --- old/khal-0.14.0/khal/ui/widgets.py 2026-03-24 17:05:07.000000000 +0100 +++ new/khal-0.14.1/khal/ui/widgets.py 2026-08-21 08:25:24.942325400 +0200 @@ -305,7 +305,7 @@ def _first_selectable(self): """return sequence number of self.contents last selectable item""" - for j in range(0, len(self._contents)): + for j in range(len(self._contents)): if self._contents[j][0].selectable(): return j return False @@ -373,7 +373,7 @@ def _first_selectable(self): """return sequence number of self._contents last selectable item""" - for j in range(0, len(self.body)): + for j in range(len(self.body)): if self.body[j].selectable(): return j return False @@ -643,8 +643,8 @@ focus_item=1, ) - urwid.WidgetDecoration.__init__(self, widget) urwid.WidgetWrap.__init__(self, self._all) + self._original_widget = widget def render(self, size, focus): inner = self._all.contents[1][0] @@ -696,7 +696,7 @@ ) urwid.WidgetWrap.__init__(self, self._all) - urwid.WidgetDecoration.__init__(self, widget) + self._original_widget = widget def render(self, size, focus): if focus: @@ -715,7 +715,7 @@ topline = urwid.AttrMap(urwid.Divider("━"), "frame") self._all = urwid.Pile([("flow", topline), widget], focus_item=1) urwid.WidgetWrap.__init__(self, self._all) - urwid.WidgetDecoration.__init__(self, widget) + self._original_widget = widget def render(self, size, focus): if focus: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/khal-0.14.0/khal/utils.py new/khal-0.14.1/khal/utils.py --- old/khal-0.14.0/khal/utils.py 2026-03-24 17:05:07.000000000 +0100 +++ new/khal-0.14.1/khal/utils.py 2026-08-21 08:25:24.942325400 +0200 @@ -197,6 +197,12 @@ 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: @@ -245,6 +251,7 @@ "end-necessary-long", "repeat-symbol", "repeat-pattern", + "alarms-list", "title", "organizer", "description", diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/khal-0.14.0/khal/version.py new/khal-0.14.1/khal/version.py --- old/khal-0.14.0/khal/version.py 2026-03-24 17:05:08.000000000 +0100 +++ new/khal-0.14.1/khal/version.py 2026-08-21 08:25:26.381694800 +0200 @@ -28,7 +28,7 @@ commit_id: COMMIT_ID __commit_id__: COMMIT_ID -__version__ = version = '0.14.0' -__version_tuple__ = version_tuple = (0, 14, 0) +__version__ = version = '0.14.1' +__version_tuple__ = version_tuple = (0, 14, 1) -__commit_id__ = commit_id = 'gd1b48422c' +__commit_id__ = commit_id = 'ge8642d7c9' diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/khal-0.14.0/khal.egg-info/PKG-INFO new/khal-0.14.1/khal.egg-info/PKG-INFO --- old/khal-0.14.0/khal.egg-info/PKG-INFO 2026-03-24 17:05:08.000000000 +0100 +++ new/khal-0.14.1/khal.egg-info/PKG-INFO 2026-08-21 08:25:26.421289200 +0200 @@ -1,6 +1,6 @@ Metadata-Version: 2.4 Name: khal -Version: 0.14.0 +Version: 0.14.1 Summary: Standards based terminal calendar Author-email: khal contributors <[email protected]> License-Expression: MIT @@ -61,7 +61,7 @@ *Khal* is a standards based CLI and terminal calendar program, able to synchronize with CalDAV_ servers through vdirsyncer_. -.. image:: http://lostpackets.de/images/khal.png +.. image:: https://lostpackets.de/images/khal.png Features -------- @@ -86,16 +86,16 @@ .. _vdir: https://vdirsyncer.readthedocs.org/en/stable/vdir.html .. _vdirsyncer: https://github.com/pimutils/vdirsyncer -.. _CalDAV: http://en.wikipedia.org/wiki/CalDAV +.. _CalDAV: https://en.wikipedia.org/wiki/CalDAV .. _github: https://github.com/pimutils/khal/ -.. __: http://en.wikipedia.org/wiki/Comparison_of_CalDAV_and_CardDAV_implementations +.. __: https://en.wikipedia.org/wiki/Comparison_of_CalDAV_and_CardDAV_implementations Documentation ------------- For khal's documentation have a look at readthedocs_. -.. _readthedocs: http://khal.readthedocs.org/ +.. _readthedocs: https://khal.readthedocs.org/ Installation ------------ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/khal-0.14.0/pyproject.toml new/khal-0.14.1/pyproject.toml --- old/khal-0.14.0/pyproject.toml 2026-03-24 17:05:07.000000000 +0100 +++ new/khal-0.14.1/pyproject.toml 2026-08-21 08:25:24.942752000 +0200 @@ -63,6 +63,12 @@ khal = "khal.cli:main_khal" ikhal = "khal.cli:main_ikhal" +[tool.pytest.ini_options] +filterwarnings = [ + # hypothesis triggers warnings while walking modules inside urwid. + "ignore:.*urwid\\.(lcd_display|html_fragment|web_display|monitored_list|listbox|treetools) is moved to.*:DeprecationWarning", +] + [build-system] requires = ["setuptools>=77", "setuptools_scm>=8"] build-backend = "setuptools.build_meta" @@ -92,6 +98,3 @@ exclude_lines = [ "if TYPE_CHECKING:", ] - -[tool.mypy] -ignore_missing_imports = true diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/khal-0.14.0/tests/cli_test.py new/khal-0.14.1/tests/cli_test.py --- old/khal-0.14.0/tests/cli_test.py 2026-03-24 17:05:07.000000000 +0100 +++ new/khal-0.14.1/tests/cli_test.py 2026-08-21 08:25:24.942902600 +0200 @@ -215,7 +215,7 @@ result = runner.invoke(main_khal, command.split()) assert not result.exception - result = runner.invoke(main_khal, "list now".split()) + result = runner.invoke(main_khal, ["list", "now"]) assert ( result.output == """Today, 01.06.2015 @@ -232,7 +232,7 @@ """ ) assert not result.exception - result = runner.invoke(main_khal, "list now --notstarted".split()) + result = runner.invoke(main_khal, ["list", "now", "--notstarted"]) assert ( result.output == """Today, 01.06.2015 @@ -246,7 +246,7 @@ ) assert not result.exception - result = runner.invoke(main_khal, "list now --once".split()) + result = runner.invoke(main_khal, ["list", "now", "--once"]) assert ( result.output == """Today, 01.06.2015 @@ -260,7 +260,7 @@ ) assert not result.exception - result = runner.invoke(main_khal, "list now --once --notstarted".split()) + result = runner.invoke(main_khal, ["list", "now", "--once", "--notstarted"]) assert ( result.output == """Today, 01.06.2015 @@ -346,9 +346,9 @@ def test_invalid_calendar(runner): runner = runner(days=2) - result = runner.invoke(main_khal, ["new"] + "-a one 18:00 myevent".split()) + result = runner.invoke(main_khal, ["new"] + ["-a", "one", "18:00", "myevent"]) assert not result.exception - result = runner.invoke(main_khal, ["new"] + "-a inexistent 18:00 myevent".split()) + result = runner.invoke(main_khal, ["new"] + ["-a", "inexistent", "18:00", "myevent"]) assert result.exception assert result.exit_code == 2 assert "Unknown calendar " in result.output @@ -527,6 +527,32 @@ 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"}\ +]}]' + print(result.output) + print(expected) + 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") @@ -560,7 +586,7 @@ def test_no_default_new(runner): runner = runner(default_calendar=False) - result = runner.invoke(main_khal, "new 18:00 beer".split()) + result = runner.invoke(main_khal, ["new", "18:00", "beer"]) assert ( "Error: Invalid value: No default calendar is configured, please provide one explicitly." ) in result.output @@ -578,7 +604,7 @@ def test_import(runner, monkeypatch): runner = runner() - result = runner.invoke(main_khal, "import -a one -a two import file.ics".split()) + result = runner.invoke(main_khal, ["import", "-a", "one", "-a", "two", "import", "file.ics"]) assert result.exception assert result.exit_code == 2 assert 'Can\'t use "--include-calendar" / "-a" more than once' in result.output @@ -992,7 +1018,7 @@ def test_new(runner): runner = runner(print_new="path") - result = runner.invoke(main_khal, "new 13.03.2016 3d Visit".split()) + result = runner.invoke(main_khal, ["new", "13.03.2016", "3d", "Visit"]) assert not result.exception assert result.output.endswith(".ics\n") assert result.output.startswith(str(runner.tmpdir)) @@ -1033,7 +1059,7 @@ def test_new_interactive(runner): runner = runner(print_new="path") - result = runner.invoke(main_khal, "new -i".split(), "Another event\n13:00 17:00\n\nNone\nn\n") + result = runner.invoke(main_khal, ["new", "-i"], "Another event\n13:00 17:00\n\nNone\nn\n") assert not result.exception assert result.exit_code == 0 @@ -1053,7 +1079,7 @@ result = runner.invoke( main_khal, - "new -i 15:00 15:30".split(), + ["new", "-i", "15:00", "15:30"], "?\ninvalid\ntwo\n" "Unicce Name\n" "\n" @@ -1079,7 +1105,7 @@ result = runner.invoke( main_khal, - "new -i".split(), + ["new", "-i"], "two\n" "new event\n" "now\n" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/khal-0.14.0/tests/controller_test.py new/khal-0.14.1/tests/controller_test.py --- old/khal-0.14.0/tests/controller_test.py 2026-03-24 17:05:07.000000000 +0100 +++ new/khal-0.14.1/tests/controller_test.py 2026-08-21 08:25:24.943333000 +0200 @@ -39,7 +39,7 @@ class TestGetAgenda: def test_new_event(self, coll_vdirs): - coll, vdirs = coll_vdirs + coll, _vdirs = coll_vdirs event = coll.create_event_from_ics(event_today, utils.cal1) coll.insert(event) assert [" a meeting :: short description\x1b[0m"] == khal_list( @@ -47,7 +47,7 @@ ) def test_new_event_day_format(self, coll_vdirs): - coll, vdirs = coll_vdirs + coll, _vdirs = coll_vdirs event = coll.create_event_from_ics(event_today, utils.cal1) coll.insert(event) assert [ @@ -61,7 +61,7 @@ event_today = event_allday_template.format( today.strftime("%Y%m%d"), tomorrow.strftime("%Y%m%d") ) - coll, vdirs = coll_vdirs + coll, _vdirs = coll_vdirs event = coll.create_event_from_ics(event_today, utils.cal1) coll.insert(event) out = khal_list(coll, conf=conf, agenda_format=event_format, datepoint=[]) @@ -72,14 +72,14 @@ def test_agenda_fail(self, coll_vdirs): with freeze_time("2016-04-10 12:33"): - coll, vdirs = coll_vdirs + coll, _vdirs = coll_vdirs with pytest.raises(exceptions.FatalError): khal_list(coll, conf=conf, agenda_format=event_format, datepoint=["xyz"]) with pytest.raises(exceptions.FatalError): khal_list(coll, conf=conf, agenda_format=event_format, datepoint=["today"]) def test_empty_recurrence(self, coll_vdirs): - coll, vidrs = coll_vdirs + coll, _vidrs = coll_vdirs coll.insert( coll.create_event_from_ics( dedent( @@ -106,7 +106,7 @@ class TestImport: def test_import(self, coll_vdirs): - coll, vdirs = coll_vdirs + coll, _vdirs = coll_vdirs view = {"event_format": "{title}"} conf = {"locale": utils.LOCALE_BERLIN, "view": view} import_ics(coll, conf, _get_text("event_rrule_recuid"), batch=True) @@ -132,7 +132,7 @@ """ Test importing events with mixed tz-aware and tz-naive datetimes. """ - coll, vdirs = coll_vdirs + coll, _vdirs = coll_vdirs view = {"event_format": "{title}"} import_ics( coll, diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/khal-0.14.0/tests/event_test.py new/khal-0.14.1/tests/event_test.py --- old/khal-0.14.0/tests/event_test.py 2026-03-24 17:05:07.000000000 +0100 +++ new/khal-0.14.1/tests/event_test.py 2026-08-21 08:25:24.943333000 +0200 @@ -596,6 +596,28 @@ 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 == "" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/khal-0.14.0/tests/khalendar_test.py new/khal-0.14.1/tests/khalendar_test.py --- old/khal-0.14.0/tests/khalendar_test.py 2026-03-24 17:05:07.000000000 +0100 +++ new/khal-0.14.1/tests/khalendar_test.py 2026-08-21 08:25:24.945348300 +0200 @@ -71,7 +71,7 @@ assert len(list(vdirs[cal1].list())) == 1 def test_sanity(self, coll_vdirs): - coll, vdirs = coll_vdirs + coll, _vdirs = coll_vdirs mtimes = {} for _ in range(100): for cal in coll._calendars: @@ -115,7 +115,7 @@ class TestVdirsyncerCompat: def test_list(self, coll_vdirs): - coll, vdirs = coll_vdirs + coll, _vdirs = coll_vdirs event = Event.fromString(_get_text("event_d"), calendar=cal1, locale=LOCALE_BERLIN) assert event.etag is None assert event.href is None @@ -164,7 +164,7 @@ assert coll.writable_names == ["home"] def test_empty(self, coll_vdirs): - coll, vdirs = coll_vdirs + coll, _vdirs = coll_vdirs start = dt.datetime.combine(today, dt.time.min) end = dt.datetime.combine(today, dt.time.max) assert list(coll.get_floating(start, end)) == [] @@ -225,7 +225,7 @@ def test_get(self, coll_vdirs): """test getting an event by its href""" - coll, vdirs = coll_vdirs + coll, _vdirs = coll_vdirs event = Event.fromString( _get_text("event_dt_simple"), href="xyz.ics", @@ -242,7 +242,7 @@ def test_change(self, coll_vdirs): """moving an event from one calendar to another""" - coll, vdirs = coll_vdirs + coll, _vdirs = coll_vdirs event = Event.fromString(_get_text("event_dt_simple"), calendar=cal1, locale=LOCALE_BERLIN) coll.insert(event, cal1) event = list(coll.get_events_on(aday))[0] @@ -255,7 +255,7 @@ def test_update_event(self, coll_vdirs): """updating one event""" - coll, vdirs = coll_vdirs + coll, _vdirs = coll_vdirs event = Event.fromString(_get_text("event_dt_simple"), calendar=cal1, locale=LOCALE_BERLIN) coll.insert(event, cal1) events = coll.get_events_on(aday) @@ -270,7 +270,7 @@ assert events[0].summary == "really simple event" def test_newevent(self, coll_vdirs): - coll, vdirs = coll_vdirs + coll, _vdirs = coll_vdirs bday = dt.datetime.combine(aday, dt.time.min) anend = bday + dt.timedelta(hours=1) event = icalendar_helpers.new_vevent( @@ -284,7 +284,7 @@ assert event.allday is False def test_modify_readonly_calendar(self, coll_vdirs): - coll, vdirs = coll_vdirs + coll, _vdirs = coll_vdirs coll._calendars[cal1]["readonly"] = True coll._calendars[cal3]["readonly"] = True event = Event.fromString(_get_text("event_dt_simple"), calendar=cal1, locale=LOCALE_BERLIN) @@ -296,7 +296,7 @@ coll.delete("href", "eteg", cal1) def test_search(self, coll_vdirs): - coll, vdirs = coll_vdirs + coll, _vdirs = coll_vdirs assert len(list(coll.search("Event"))) == 0 event = Event.fromString(_get_text("event_dt_simple"), calendar=cal1, locale=LOCALE_BERLIN) coll.insert(event, cal1) @@ -311,7 +311,7 @@ def test_search_recurrence_id_only(self, coll_vdirs): """test searching for recurring events which only have a recuid event, and no master""" - coll, vdirs = coll_vdirs + coll, _vdirs = coll_vdirs assert len(list(coll.search("Event"))) == 0 event = Event.fromString( _get_text("event_dt_recuid_no_master"), calendar=cal1, locale=LOCALE_BERLIN @@ -322,7 +322,7 @@ def test_search_recurrence_id_only_multi(self, coll_vdirs): """test searching for recurring events which only have a recuid event, and no master""" - coll, vdirs = coll_vdirs + coll, _vdirs = coll_vdirs assert len(list(coll.search("Event"))) == 0 event = Event.fromString( _get_text("event_dt_multi_recuid_no_master"), calendar=cal1, locale=LOCALE_BERLIN @@ -386,7 +386,7 @@ def test_invalid_timezones(self, coll_vdirs): """testing if we can delete any of two events in two different calendars with the same filename""" - coll, vdirs = coll_vdirs + coll, _vdirs = coll_vdirs event = Event.fromString(_get_text("invalid_tzoffset"), calendar=cal1, locale=LOCALE_BERLIN) coll.insert(event, cal1) events = sorted(coll.search("Event")) @@ -515,7 +515,7 @@ def test_only_update_old_event(coll_vdirs, monkeypatch, sleep_time): coll, vdirs = coll_vdirs - href_one, etag_one = vdirs[cal1].upload( + _href_one, _etag_one = vdirs[cal1].upload( coll.create_event_from_ics( dedent(""" BEGIN:VEVENT @@ -531,7 +531,7 @@ sleep(sleep_time) # Make sure we get a new etag for meeting-two - href_two, etag_two = vdirs[cal1].upload( + _href_two, _etag_two = vdirs[cal1].upload( coll.create_event_from_ics( dedent(""" BEGIN:VEVENT @@ -559,7 +559,7 @@ monkeypatch.setattr(coll, "_update_vevent", _update_vevent) - href_three, etag_three = vdirs[cal1].upload( + href_three, _etag_three = vdirs[cal1].upload( coll.create_event_from_ics( dedent(""" BEGIN:VEVENT diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/khal-0.14.0/tests/parse_datetime_test.py new/khal-0.14.1/tests/parse_datetime_test.py --- old/khal-0.14.0/tests/parse_datetime_test.py 2026-03-24 17:05:07.000000000 +0100 +++ new/khal-0.14.1/tests/parse_datetime_test.py 2026-08-21 08:25:24.945348300 +0200 @@ -144,31 +144,31 @@ @freeze_time("2016-9-19T8:00") def test_tomorrow(self): assert (dt.datetime(2016, 9, 20, 16), False) == guessdatetimefstr( - "tomorrow 16:00 16:00".split(), locale=LOCALE_BERLIN + ["tomorrow", "16:00", "16:00"], locale=LOCALE_BERLIN ) @freeze_time("2016-9-19T8:00") def test_time_tomorrow(self): assert (dt.datetime(2016, 9, 20, 16), False) == guessdatetimefstr( - "16:00".split(), locale=LOCALE_BERLIN, default_day=dt.date(2016, 9, 20) + ["16:00"], locale=LOCALE_BERLIN, default_day=dt.date(2016, 9, 20) ) @freeze_time("2016-9-19T8:00") def test_time_yesterday(self): assert (dt.datetime(2016, 9, 18, 16), False) == guessdatetimefstr( - "Yesterday 16:00".split(), locale=LOCALE_BERLIN, default_day=dt.datetime.today() + ["Yesterday", "16:00"], locale=LOCALE_BERLIN, default_day=dt.datetime.today() ) @freeze_time("2016-9-19") def test_time_weekday(self): assert (dt.datetime(2016, 9, 23, 16), False) == guessdatetimefstr( - "Friday 16:00".split(), locale=LOCALE_BERLIN, default_day=dt.datetime.today() + ["Friday", "16:00"], locale=LOCALE_BERLIN, default_day=dt.datetime.today() ) @freeze_time("2016-9-19 17:53") def test_time_now(self): assert (dt.datetime(2016, 9, 19, 17, 53), False) == guessdatetimefstr( - "now".split(), locale=LOCALE_BERLIN, default_day=dt.datetime.today() + ["now"], locale=LOCALE_BERLIN, default_day=dt.datetime.today() ) @freeze_time("2016-12-30 17:53") @@ -182,10 +182,10 @@ "longdatetimeformat": "", } assert (dt.datetime(2017, 1, 1), True) == guessdatetimefstr( - "2017-1-1".split(), locale=locale, default_day=dt.datetime.today() + ["2017-1-1"], locale=locale, default_day=dt.datetime.today() ) assert (dt.datetime(2017, 1, 1, 16, 30), False) == guessdatetimefstr( - "2017-1-1 16:30".split(), locale=locale, default_day=dt.datetime.today() + ["2017-1-1", "16:30"], locale=locale, default_day=dt.datetime.today() ) @freeze_time("2016-12-30 17:53") @@ -200,10 +200,10 @@ "longdatetimeformat": "%Y-%m-%d %H:%M", } assert (dt.datetime(2017, 1, 1), True) == guessdatetimefstr( - "2017-1-1".split(), locale=locale, default_day=dt.datetime.today() + ["2017-1-1"], locale=locale, default_day=dt.datetime.today() ) assert (dt.datetime(2017, 1, 1, 16, 30), False) == guessdatetimefstr( - "2017-1-1 16:30".split(), locale=locale, default_day=dt.datetime.today() + ["2017-1-1", "16:30"], locale=locale, default_day=dt.datetime.today() ) @@ -539,9 +539,8 @@ def test_leap_year(): for data_list, vevent in test_set_leap_year: - with freeze_time("1999-1-1"): - with pytest.raises(DateTimeParseError): - event = _construct_event(data_list.split(), locale=LOCALE_BERLIN) + with freeze_time("1999-1-1"), pytest.raises(DateTimeParseError): + event = _construct_event(data_list.split(), locale=LOCALE_BERLIN) with freeze_time("2016-1-1 20:21:22"): event = _construct_event(data_list.split(), locale=LOCALE_BERLIN) assert _replace_uid(event).to_ical() == vevent diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/khal-0.14.0/tests/ui/test_widgets.py new/khal-0.14.1/tests/ui/test_widgets.py --- old/khal-0.14.0/tests/ui/test_widgets.py 2026-03-24 17:05:07.000000000 +0100 +++ new/khal-0.14.1/tests/ui/test_widgets.py 2026-08-21 08:25:24.946356000 +0200 @@ -1,4 +1,12 @@ -from khal.ui.widgets import delete_last_word +import pytest +import urwid + +from khal.ui.widgets import ( + FocusLineBoxColor, + FocusLineBoxTop, + FocusLineBoxWidth, + delete_last_word, +) def test_delete_last_word(): @@ -27,3 +35,12 @@ for org, short, number in tests: assert delete_last_word(org, number) == short + + [email protected]("cls", [FocusLineBoxColor, FocusLineBoxTop, FocusLineBoxWidth]) +def test_focus_line_box(cls): + inner = urwid.Filler(urwid.Text("test")) + box = cls(inner) + assert box.original_widget is inner + box.render((20, 10), focus=False) + box.render((20, 10), focus=True)
