Eli Zaretskii <eliz@HIDDEN>
to control <at> debbugs.gnu.org.
Full text available.Received: (at 80426) by debbugs.gnu.org; 28 Feb 2026 14:01:08 +0000 From debbugs-submit-bounces <at> debbugs.gnu.org Sat Feb 28 09:01:08 2026 Received: from localhost ([127.0.0.1]:35738 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from <debbugs-submit-bounces <at> debbugs.gnu.org>) id 1vwKsV-0008H7-Ol for submit <at> debbugs.gnu.org; Sat, 28 Feb 2026 09:01:08 -0500 Received: from eggs.gnu.org ([2001:470:142:3::10]:46652) by debbugs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.84_2) (envelope-from <eliz@HIDDEN>) id 1vwKsR-0008GE-NI for 80426 <at> debbugs.gnu.org; Sat, 28 Feb 2026 09:01:05 -0500 Received: from fencepost.gnu.org ([2001:470:142:3::e]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from <eliz@HIDDEN>) id 1vwKsI-0003fr-9a; Sat, 28 Feb 2026 09:00:55 -0500 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=gnu.org; s=fencepost-gnu-org; h=References:Subject:In-Reply-To:To:From:Date: mime-version; bh=IxQZStLvZoDh4aEY1EhjNyb6FcLlPZx7sB3j+NbAZK4=; b=aVadXnzN6ETO 97AGBz88RuA94eG+94t6oqg4GqfMd7O19rJO3f+i8zIXq+/2y/mob1NPbu3Mu2vhtRnNQ417cA+ra 7Rg0krjTBjCIXj9JlU9IXh3G5x+AgQc+jr3zJAzfPbIJBSaRURRneSljJ4vXROTHX7eAjhFXiRekw RbdPfvmMeX99pcP0RSCu739iYYgTVyrNNeJ4VUqDHwIfw4AfHV+1OP+nT7eeX/fydfHtuxxApbQFj 7rNkXrZnbC4u+US48D7n5iAz0GxnqFGufWtlpqBgIVrWt93qZMdTxWi6GJ2NtEY/Irugt6wLEU3zW pvCP4e8Ls/lmHiGTsX1g8Q==; Date: Sat, 28 Feb 2026 15:59:31 +0200 Message-Id: <868qcd2cl8.fsf@HIDDEN> From: Eli Zaretskii <eliz@HIDDEN> To: Richard Lawrence <rwl@HIDDEN> In-Reply-To: <87ldgrefo6.fsf@HIDDEN> (message from Richard Lawrence on Tue, 17 Feb 2026 13:08:09 +0100) Subject: Re: bug#80426: 31.0.50; Update for gnus-icalendar.el References: <87ldgrefo6.fsf@HIDDEN> X-Spam-Score: -2.3 (--) X-Debbugs-Envelope-To: 80426 Cc: eric@HIDDEN, yamaoka@HIDDEN, 80426 <at> debbugs.gnu.org, cohen@HIDDEN X-BeenThere: debbugs-submit <at> debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list List-Id: <debbugs-submit.debbugs.gnu.org> List-Unsubscribe: <https://debbugs.gnu.org/cgi-bin/mailman/options/debbugs-submit>, <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=unsubscribe> List-Archive: <https://debbugs.gnu.org/cgi-bin/mailman/private/debbugs-submit/> List-Post: <mailto:debbugs-submit <at> debbugs.gnu.org> List-Help: <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=help> List-Subscribe: <https://debbugs.gnu.org/cgi-bin/mailman/listinfo/debbugs-submit>, <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=subscribe> Errors-To: debbugs-submit-bounces <at> debbugs.gnu.org Sender: "Debbugs-submit" <debbugs-submit-bounces <at> debbugs.gnu.org> X-Spam-Score: -3.3 (---) > Cc: Andrew G Cohen <cohen@HIDDEN>, > Eric Abrahamsen <eric@HIDDEN>, Katsumi Yamaoka <yamaoka@HIDDEN> > From: Richard Lawrence <rwl@HIDDEN> > Date: Tue, 17 Feb 2026 13:08:09 +0100 > > I'm reposting this here as a new bug report with some Gnus folks in > X-Debbugs-CC, as was recommended to me on emacs-devel: > > Now that the new iCalendar library has been merged, gnus-icalendar.el > needs an update. I sent the attached patch to bugs@HIDDEN at the end > of December but have so far received no response. Is anyone here able > to review the patch? Or do you know another way to get in touch with > someone who can? > > Thanks! Here's my original message for context: > > > Once [the library] is merged, byte-compiling gnus-icalendar.el > > will produce obsolescence warnings, since it calls newly-obsolete > > functions from icalendar.el. > > > > I attach a draft patch which updates gnus-icalendar.el to use the new > > library. This patch is a fairly minimal update which fixes the warnings > > and gets the tests working again. (gnus-icalendar.el doesn't seem to > > have gotten a lot of love over the years; I've tried to give it some > > here, but there's surely more that could be done.) Please let me know > > if you'd like me to make any further changes. Thanks. Would Gnus users please review this and comment?
bug-gnu-emacs@HIDDEN:bug#80426; Package emacs.
Full text available.
Received: (at submit) by debbugs.gnu.org; 17 Feb 2026 12:08:39 +0000
From debbugs-submit-bounces <at> debbugs.gnu.org Tue Feb 17 07:08:38 2026
Received: from localhost ([127.0.0.1]:44557 helo=debbugs.gnu.org)
by debbugs.gnu.org with esmtp (Exim 4.84_2)
(envelope-from <debbugs-submit-bounces <at> debbugs.gnu.org>)
id 1vsJsb-0002uw-8C
for submit <at> debbugs.gnu.org; Tue, 17 Feb 2026 07:08:38 -0500
Received: from lists.gnu.org ([2001:470:142::17]:35690)
by debbugs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256)
(Exim 4.84_2) (envelope-from <rwl@HIDDEN>)
id 1vsJsX-0002uU-32
for submit <at> debbugs.gnu.org; Tue, 17 Feb 2026 07:08:35 -0500
Received: from eggs.gnu.org ([2001:470:142:3::10])
by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256)
(Exim 4.90_1) (envelope-from <rwl@HIDDEN>)
id 1vsJsR-0000A8-2n
for bug-gnu-emacs@HIDDEN; Tue, 17 Feb 2026 07:08:27 -0500
Received: from fout-a4-smtp.messagingengine.com ([103.168.172.147])
by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256)
(Exim 4.90_1) (envelope-from <rwl@HIDDEN>)
id 1vsJsJ-0005rf-L8
for bug-gnu-emacs@HIDDEN; Tue, 17 Feb 2026 07:08:24 -0500
Received: from phl-compute-10.internal (phl-compute-10.internal [10.202.2.50])
by mailfout.phl.internal (Postfix) with ESMTP id D0DACEC0653
for <bug-gnu-emacs@HIDDEN>; Tue, 17 Feb 2026 07:08:15 -0500 (EST)
Received: from phl-frontend-03 ([10.202.2.162])
by phl-compute-10.internal (MEProxy); Tue, 17 Feb 2026 07:08:15 -0500
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=
recursewithless.net; h=cc:content-type:content-type:date:date
:from:from:in-reply-to:message-id:mime-version:reply-to:subject
:subject:to:to; s=fm2; t=1771330095; x=1771416495; bh=R6216Kd1GA
QKeODCFyE8riN6oGWodMvsvGvl7eV81+w=; b=PIBltm0qsZiJr7BPHmJ7yQD5ZX
jcrYAwHA/JrjrM1IbcMXUJVmhq+cfhsDn1JjGXdWMVBMwgKKZphjL/ZFzArLuguo
m3i16VdAdMVwPF28rfw9nAtwb29l4kUJQuqEg20cpH4nMbYnH7ns14zdtzNCN5x/
EISRAQpwQoeETbgHnnp1HyaFU7YOR1kkaL/t4R1jWG48omYYJO5kJ+vChB+K9T0O
D7DNlzdIQLeePVPP4bM2diT/xFVGMuV4SJMi1etut2OPJWLj/DPV7uFo/F1Vb/tV
EwPHEgy0Irmi1nWuIg7sBBYm/AuJtYICRjD7ZTBe0n24VJZc6YCr0ag7GCtg==
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=
messagingengine.com; h=cc:content-type:content-type:date:date
:feedback-id:feedback-id:from:from:in-reply-to:message-id
:mime-version:reply-to:subject:subject:to:to:x-me-proxy
:x-me-sender:x-me-sender:x-sasl-enc; s=fm3; t=1771330095; x=
1771416495; bh=R6216Kd1GAQKeODCFyE8riN6oGWodMvsvGvl7eV81+w=; b=v
SBXug6x1DyEfM5s4Geg1ZU/DOzn5cIJV28Gv+hNCWO9JOC0IZ+PKp+oL1aQeCPzh
ugxEiP18ADqLPbWRzmrzlnEhleSZTmryl34xaN8n3iXHPPIa10XQXBel3cGPnwXm
9jTei/6Hvh3tY90joQ529YS1vxzlGWpIYIOX99iKm/uF9liaxsE4pUpa9DLAZ3bD
pGJ8TYZ1dj/E7D2KyUfy1i4fMonYQuCeHYbJ5a9grRdjzD4Hs/bE6GLK4sAOTCQZ
Ulg0WvVXCDSDz3/XkbBT96tJpH522ow97x/mArF4KgskvAO4SytzE6Wq0RMOQDgE
/uKStJI3sEwYEaINFeVUA==
X-ME-Sender: <xms:L1qUac3hrp0wcM1TiO0eW-cgAHHbYoSJEbdAmwA3JX6ii7suhoIzOg>
<xme:L1qUac_j7aPINoRsrZQzKLJnFau5T4aWAyxSawVfO-F1uRCFbiBVRYN8POb1vdYjm
WUnAScqXuXfcBHKl-7UqBIHTPJh5wkrrWv4iGVdJ2YFKX0MLvn2Ig>
X-ME-Received: <xmr:L1qUaf_FehS9QFdSsjbFmqJ3ryzAtWdrWac3HX8pK51z03dUWtSARU21WRF1FWV8G4C506Xve1q-2p7wJYvQIxo1avdWngW7dqP9yDO2F7w>
X-ME-Proxy-Cause: gggruggvucftvghtrhhoucdtuddrgeefgedrtddtgddvudeljedvucetufdoteggodetrf
dotffvucfrrhhofhhilhgvmecuhfgrshhtofgrihhlpdfurfetoffkrfgpnffqhgenuceu
rghilhhouhhtmecufedttdenucenucfjughrpefhvffufffkgggtsehmtderredttddtne
cuhfhrohhmpeftihgthhgrrhguucfnrgifrhgvnhgtvgcuoehrfihlsehrvggtuhhrshgv
fihithhhlhgvshhsrdhnvghtqeenucggtffrrghtthgvrhhnpeeiudelffehkefhiefhte
egkeevleeiteevhfetgeeggfduveeujeevgefhkeelleenucevlhhushhtvghrufhiiigv
pedtnecurfgrrhgrmhepmhgrihhlfhhrohhmpehrfihlsehrvggtuhhrshgvfihithhhlh
gvshhsrdhnvghtpdhnsggprhgtphhtthhopedupdhmohguvgepshhmthhpohhuthdprhgt
phhtthhopegsuhhgqdhgnhhuqdgvmhgrtghssehgnhhurdhorhhg
X-ME-Proxy: <xmx:L1qUaSp83VU-pdqp5kDHKX7wvicJ4U3O_D4yivZ9EeEJD3_EXMvlsw>
<xmx:L1qUaSoFtuttW6rb-fk6CNwmHr8uSSw6k2MZnkS82wFKztp5LwERnw>
<xmx:L1qUaQlwUXwayaKELHiiPyPBA0A70mOFF45J250vHrPOoOdhPTFIIA>
<xmx:L1qUaUG_xdAtaNozwLBd8Vm5_TA1XYRs4v7wozLLsXMYtiupqvhALg>
<xmx:L1qUae11pvsB8ALoscVlUPY4NNrsNO3uWMShRtbDxjLYOkI0l_EfFQc_>
Feedback-ID: if7394488:Fastmail
Received: by mail.messagingengine.com (Postfix) with ESMTPA for
<bug-gnu-emacs@HIDDEN>; Tue, 17 Feb 2026 07:08:15 -0500 (EST)
From: Richard Lawrence <rwl@HIDDEN>
To: bug-gnu-emacs@HIDDEN
Subject: 31.0.50; Update for gnus-icalendar.el
X-Debbugs-Cc: Andrew G Cohen <cohen@HIDDEN>, Eric Abrahamsen
<eric@HIDDEN>, Katsumi Yamaoka <yamaoka@HIDDEN>
Date: Tue, 17 Feb 2026 13:08:09 +0100
Message-ID: <87ldgrefo6.fsf@HIDDEN>
MIME-Version: 1.0
Content-Type: multipart/mixed; boundary="=-=-="
Received-SPF: pass client-ip=103.168.172.147;
envelope-from=rwl@HIDDEN; helo=fout-a4-smtp.messagingengine.com
X-Spam_score_int: -27
X-Spam_score: -2.8
X-Spam_bar: --
X-Spam_report: (-2.8 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1,
DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1,
RCVD_IN_DNSWL_LOW=-0.7, RCVD_IN_VALIDITY_RPBL_BLOCKED=0.001,
RCVD_IN_VALIDITY_SAFE_BLOCKED=0.001, SPF_HELO_PASS=-0.001, SPF_PASS=-0.001,
T_FILL_THIS_FORM_FRAUD_PHISH=0.01,
T_FILL_THIS_FORM_SHORT=0.01 autolearn=ham autolearn_force=no
X-Spam_action: no action
X-Spam-Score: 0.7 (/)
X-Debbugs-Envelope-To: submit
X-BeenThere: debbugs-submit <at> debbugs.gnu.org
X-Mailman-Version: 2.1.18
Precedence: list
List-Id: <debbugs-submit.debbugs.gnu.org>
List-Unsubscribe: <https://debbugs.gnu.org/cgi-bin/mailman/options/debbugs-submit>,
<mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=unsubscribe>
List-Archive: <https://debbugs.gnu.org/cgi-bin/mailman/private/debbugs-submit/>
List-Post: <mailto:debbugs-submit <at> debbugs.gnu.org>
List-Help: <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=help>
List-Subscribe: <https://debbugs.gnu.org/cgi-bin/mailman/listinfo/debbugs-submit>,
<mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=subscribe>
Errors-To: debbugs-submit-bounces <at> debbugs.gnu.org
Sender: "Debbugs-submit" <debbugs-submit-bounces <at> debbugs.gnu.org>
X-Spam-Score: -0.3 (/)
--=-=-=
Content-Type: text/plain
I'm reposting this here as a new bug report with some Gnus folks in
X-Debbugs-CC, as was recommended to me on emacs-devel:
Now that the new iCalendar library has been merged, gnus-icalendar.el
needs an update. I sent the attached patch to bugs@HIDDEN at the end
of December but have so far received no response. Is anyone here able
to review the patch? Or do you know another way to get in touch with
someone who can?
Thanks! Here's my original message for context:
> Once [the library] is merged, byte-compiling gnus-icalendar.el
> will produce obsolescence warnings, since it calls newly-obsolete
> functions from icalendar.el.
>
> I attach a draft patch which updates gnus-icalendar.el to use the new
> library. This patch is a fairly minimal update which fixes the warnings
> and gets the tests working again. (gnus-icalendar.el doesn't seem to
> have gotten a lot of love over the years; I've tried to give it some
> here, but there's surely more that could be done.) Please let me know
> if you'd like me to make any further changes.
--
Best,
Richard
--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
filename=0001-Update-gnus-icalendar-to-use-new-iCalendar-library.patch
From cf7365b5db528254b1a9b596481c0466e505ba35 Mon Sep 17 00:00:00 2001
From: Richard Lawrence <rwl@HIDDEN>
Date: Tue, 16 Dec 2025 10:39:19 +0100
Subject: [PATCH] Update gnus-icalendar to use new iCalendar library
This change updates gnus-icalendar.el to use the new iCalendar library
instead of obsolete functions from icalendar.el.
* lisp/gnus/gnus-icalendar.el
(gnus-icalendar-event)
(gnus-icalendar-event:recurring-p)
(gnus-icalendar-event:recurring-interval)
(gnus-icalendar-event:recurring-days)
(gnus-icalendar-event--find-attendee)
(gnus-icalendar-event-from-ical)
(gnus-icalendar-event-from-buffer)
(gnus-icalendar-event--build-reply)
(gnus-icalendar-event-reply-from-buffer)
(gnus-icalendar-event:org-repeat): Reimplement using new iCalendar functions.
(gnus-icalendar-event--attendees-by-type): Rename from
`gnus-icalendar-event--get-attendee-names'.
(gnus-icalendar-event--build-reply): Rename from
`gnus-icalendar-event--build-reply-event-body'.
* test/lisp/gnus/gnus-icalendar-tests.el: Update tests.
---
lisp/gnus/gnus-icalendar.el | 458 +++++++++++--------------
test/lisp/gnus/gnus-icalendar-tests.el | 82 ++++-
2 files changed, 264 insertions(+), 276 deletions(-)
diff --git a/lisp/gnus/gnus-icalendar.el b/lisp/gnus/gnus-icalendar.el
index a3ca1925109..791c87315d4 100644
--- a/lisp/gnus/gnus-icalendar.el
+++ b/lisp/gnus/gnus-icalendar.el
@@ -36,6 +36,10 @@
;;; Code:
(require 'icalendar)
+(require 'icalendar-parser)
+(eval-when-compile (require 'icalendar-macs))
+(require 'icalendar-ast)
+(require 'icalendar-utils)
(require 'eieio)
(require 'gmm-utils)
(require 'mm-decode)
@@ -82,8 +86,8 @@ gnus-icalendar-event
:type (or null t))
(recur :initarg :recur
:accessor gnus-icalendar-event:recur
- :initform ""
- :type (or null string))
+ :initform nil
+ :type (or null list))
(uid :initarg :uid
:accessor gnus-icalendar-event:uid
:type string)
@@ -127,295 +131,211 @@ gnus-icalendar-event:recurring-p
(cl-defmethod gnus-icalendar-event:recurring-freq ((event gnus-icalendar-event))
"Return recurring frequency of EVENT."
- (let ((rrule (gnus-icalendar-event:recur event)))
- (string-match "FREQ=\\([[:alpha:]]+\\)" rrule)
- (match-string 1 rrule)))
+ (ical:recur-freq (gnus-icalendar-event:recur event)))
(cl-defmethod gnus-icalendar-event:recurring-interval ((event gnus-icalendar-event))
"Return recurring interval of EVENT."
- (let ((rrule (gnus-icalendar-event:recur event))
- (default-interval "1"))
-
- (if (string-match "INTERVAL=\\([[:digit:]]+\\)" rrule)
- (match-string 1 rrule)
- default-interval)))
+ (ical:recur-interval-size (gnus-icalendar-event:recur event)))
(cl-defmethod gnus-icalendar-event:recurring-days ((event gnus-icalendar-event))
"Return, when available, the week day numbers on which the EVENT recurs."
- (let ((rrule (gnus-icalendar-event:recur event))
- (weekday-map '(("SU" . 0)
- ("MO" . 1)
- ("TU" . 2)
- ("WE" . 3)
- ("TH" . 4)
- ("FR" . 5)
- ("SA" . 6))))
- (when (and rrule (string-match "BYDAY=\\([^;]+\\)" rrule))
- (let ((bydays (split-string (match-string 1 rrule) ",")))
- (seq-map
- (lambda (x) (cdr (assoc x weekday-map)))
- (seq-filter (lambda (x) (string-match "^[A-Z]\\{2\\}$" x)) bydays))))))
+ (let ((rrule (gnus-icalendar-event:recur event)))
+ (when rrule
+ (mapcar (lambda (el) (if (consp el) (car el) el))
+ (ical:recur-by* 'BYDAY rrule)))))
(cl-defmethod gnus-icalendar-event:start ((event gnus-icalendar-event))
(format-time-string "%Y-%m-%d %H:%M" (gnus-icalendar-event:start-time event)))
-(defun gnus-icalendar-event--decode-datefield (event field zone-map)
- (let* ((dtdate (icalendar--get-event-property event field))
- (dtdate-zone (icalendar--find-time-zone
- (icalendar--get-event-property-attributes
- event field) zone-map))
- (dtdate-dec (icalendar--decode-isodatetime dtdate nil dtdate-zone)))
- (when dtdate-dec (encode-time dtdate-dec))))
-
-(defun gnus-icalendar-event--find-attendee (ical name-or-email)
- (let* ((event (car (icalendar--all-events ical)))
- (event-props (caddr event)))
- (cl-labels ((attendee-name (att) (plist-get (cadr att) 'CN))
- (attendee-email
- (att)
- (replace-regexp-in-string "^.*MAILTO:" "" (caddr att)))
- (attendee-prop-matches-p
- (prop)
- (and (eq (car prop) 'ATTENDEE)
- (or (member (attendee-name prop) name-or-email)
- (let ((att-email (attendee-email prop)))
- (gnus-icalendar-find-if
- (lambda (str-or-fun)
- (if (functionp str-or-fun)
- (funcall str-or-fun att-email)
- (string-match str-or-fun att-email)))
- name-or-email))))))
- (gnus-icalendar-find-if #'attendee-prop-matches-p event-props))))
-
-(defun gnus-icalendar-event--get-attendee-names (ical)
- (let* ((event (car (icalendar--all-events ical)))
- (attendee-props (seq-filter
- (lambda (p) (eq (car p) 'ATTENDEE))
- (caddr event))))
-
- (cl-labels
- ((attendee-role (prop)
- ;; RFC5546: default ROLE is REQ-PARTICIPANT
- (and prop
- (or (plist-get (cadr prop) 'ROLE)
- "REQ-PARTICIPANT")))
- (attendee-name
- (prop)
- (or (plist-get (cadr prop) 'CN)
- (replace-regexp-in-string "^.*MAILTO:" "" (caddr prop))))
- (attendees-by-type (type)
- (seq-filter
- (lambda (p) (string= (attendee-role p) type))
- attendee-props))
- (attendee-names-by-type
- (type)
- (mapcar #'attendee-name (attendees-by-type type))))
- (list
- (attendee-names-by-type "REQ-PARTICIPANT")
- (attendee-names-by-type "OPT-PARTICIPANT")))))
-
-(defun gnus-icalendar-event-from-ical (ical &optional attendee-name-or-email)
- (let* ((event (car (icalendar--all-events ical)))
- (organizer (replace-regexp-in-string
- "^.*MAILTO:" ""
- (or (icalendar--get-event-property event 'ORGANIZER) "")))
- (prop-map '((summary . SUMMARY)
- (description . DESCRIPTION)
- (location . LOCATION)
- (recur . RRULE)
- (uid . UID)))
- (method (caddr (assoc 'METHOD (caddr (car (nreverse ical))))))
- (attendee (when attendee-name-or-email
- (gnus-icalendar-event--find-attendee
- ical attendee-name-or-email)))
- (attendee-names (gnus-icalendar-event--get-attendee-names ical))
+(defun gnus-icalendar-event--find-attendee (attendees ids)
+ "Return the first `icalendar-attendee' in ATTENDEES matching IDS.
+IDS should be a list of strings. The first attendee is returned whose
+name (as `icalendar-cnparam') or email address (without \"mailto:\")
+is a member of IDS."
+ (catch 'found
+ (dolist (attendee attendees)
+ (ical:with-property attendee ((ical:cnparam :value name))
+ (let ((email (ical:strip-mailto value)))
+ (when (or (member name ids)
+ (member email ids))
+ (throw 'found attendee)))))))
+
+(defun gnus-icalendar-event--attendees-by-type (attendees)
+ "Return lists of required and optional participants in ATTENDEES.
+ATTENDEES must be a list of `icalendar-attendee' nodes. The returned
+list has the form (REQUIRED OPTIONAL), where each is a list of names or
+email addresses."
+ (let (required optional)
+ (dolist (attendee attendees)
+ (ical:with-property attendee ((ical:roleparam :value role))
+ (when (equal role "REQ-PARTICIPANT")
+ (push attendee required))
+ (when (equal role "OPT-PARTICIPANT")
+ (push attendee optional))))
+ (list (nreverse required)
+ (nreverse optional))))
+
+(defun gnus-icalendar-event-from-ical (vcalendar &optional ids)
+ "Initialize an event instance with the first `icalendar-vevent' in VCALENDAR.
+IDS should be a list of strings representing names and email addresses
+by which to identify an `icalendar-attendee' in the event as the
+recipient."
+ (ical:with-component vcalendar
+ ((ical:vevent vevent)
+ (ical:method :value method))
+ (ical:with-component vevent
+ ((ical:organizer :value organizer)
+ (ical:attendee :all attendees)
+ (ical:summary :value summary)
+ (ical:description :value description)
+ (ical:dtstart :value dtstart)
+ (ical:dtend :value dtend)
+ (ical:location :value location)
+ (ical:rrule :value rrule)
+ (ical:uid :value uid))
+
+ (let* ((attendee (when ids (gnus-icalendar-event--find-attendee attendees ids)))
+ (rsvp-p (ical:with-param-of attendee 'ical:rsvpparam))
;; RFC5546: default ROLE is REQ-PARTICIPANT
- (role (and attendee
- (or (plist-get (cadr attendee) 'ROLE)
- "REQ-PARTICIPANT")))
+ (role (when attendee
+ (or (ical:with-param-of attendee 'ical:roleparam)
+ "REQ-PARTICIPANT")))
(participation-type (pcase role
("REQ-PARTICIPANT" 'required)
("OPT-PARTICIPANT" 'optional)
(_ 'non-participant)))
- (zone-map (icalendar--convert-all-timezones ical))
+ (req/opt (gnus-icalendar-event--attendees-by-type attendees))
(args
(list :method method
- :organizer organizer
- :start-time (gnus-icalendar-event--decode-datefield
- event 'DTSTART zone-map)
- :end-time (gnus-icalendar-event--decode-datefield
- event 'DTEND zone-map)
- :rsvp (string= (plist-get (cadr attendee) 'RSVP) "TRUE")
+ :organizer (when organizer (ical:strip-mailto organizer))
+ :summary summary
+ :description description
+ :location location
+ :recur rrule
+ :start-time (encode-time dtstart)
+ :end-time (encode-time dtend)
+ :rsvp rsvp-p
:participation-type participation-type
- :req-participants (car attendee-names)
- :opt-participants (cadr attendee-names)))
- (event-class
- (cond
- ((string= method "REQUEST") 'gnus-icalendar-event-request)
- ((string= method "CANCEL") 'gnus-icalendar-event-cancel)
- ((string= method "REPLY") 'gnus-icalendar-event-reply)
- (t 'gnus-icalendar-event))))
- (cl-labels
- ((map-property
- (prop)
- (let ((value (icalendar--get-event-property event prop)))
- (when value
- ;; ugly, but cannot get
- ;;replace-regexp-in-string work with "\\" as
- ;;REP, plus we should also handle "\\;"
- (string-replace
- "\\," ","
- (string-replace
- "\\n" "\n" (substring-no-properties value))))))
- (accumulate-args
- (mapping)
- (cl-destructuring-bind (slot . ical-property) mapping
- (setq args (append (list
- (intern (concat ":" (symbol-name slot)))
- (map-property ical-property))
- args)))))
- (mapc #'accumulate-args prop-map)
- (apply
- #'make-instance
- event-class
- (cl-loop for slot in (eieio-class-slots event-class)
- for keyword = (intern
- (format ":%s" (eieio-slot-descriptor-name slot)))
- when (plist-member args keyword)
- append (list keyword
- (if (eq keyword :uid)
- ;; The UID has to be a string.
- (or (plist-get args keyword) "")
- (plist-get args keyword))))))))
-
-(defun gnus-icalendar-event-from-buffer (buf &optional attendee-name-or-email)
+ :req-participants (car req/opt)
+ :opt-participants (cadr req/opt)
+ :uid (or uid ""))) ; UID must be a string
+ (event-class (pcase method
+ ("REQUEST" 'gnus-icalendar-event-request)
+ ("CANCEL" 'gnus-icalendar-event-cancel)
+ ("REPLY" 'gnus-icalendar-event-reply)
+ (_ 'gnus-icalendar-event))))
+ ;; Initialize and return the instance:
+ (apply
+ #'make-instance
+ event-class
+ (cl-loop for slot in (eieio-class-slots event-class)
+ for keyword = (intern
+ (format ":%s" (eieio-slot-descriptor-name slot)))
+ when (plist-member args keyword)
+ append (list keyword (plist-get args keyword))))))))
+
+(defun gnus-icalendar-event-from-buffer (buf &optional ids)
"Parse RFC5545 iCalendar in buffer BUF and return an event object.
Return a gnus-icalendar-event object representing the first event
contained in the invitation. Return nil for calendars without an
event entry.
-ATTENDEE-NAME-OR-EMAIL is a list of strings that will be matched
-against the event's attendee names and emails. Invitation rsvp
-status will be retrieved from the first matching attendee record."
- (let ((ical (with-current-buffer (icalendar--get-unfolded-buffer (get-buffer buf))
- (goto-char (point-min))
- (icalendar--read-element nil nil))))
-
- (when ical
- (gnus-icalendar-event-from-ical ical attendee-name-or-email))))
+IDS is a list of strings that identify the recipient
+`icalendar-attendee' by name or email address. Invitation rsvp status
+will be retrieved from the first matching attendee record."
+ (let ((vcalendar (ical:parse buf)))
+ (when vcalendar
+ (gnus-icalendar-event-from-ical vcalendar ids))))
;;;
;;; gnus-icalendar-event-reply
;;;
-(defun gnus-icalendar-event--build-reply-event-body (ical-request status identities &optional comment)
+(defun gnus-icalendar-event--build-reply (vcalendar status ids &optional comment)
+ "Return an `icalendar-vcalendar' based on VCALENDAR with updated STATUS.
+STATUS should one of \\='accepted, \\='declined, or \\='tentative. The
+recipient whose participation status is updated to STATUS is identified
+in EVENT by finding an `icalendar-attendee' whose name or email address
+matches one of the strings in IDS. If no such attendee is found, a new
+`icalendar-attendee' is added from the values of `user-mail-address' and
+`user-full-name'. COMMENT, if provided, will be added as an
+`icalendar-comment' to the returned event."
(let ((summary-status (capitalize (symbol-name status)))
(attendee-status (upcase (symbol-name status)))
- reply-event-lines)
- (cl-labels
- ((update-summary
- (line)
- (if (string-match "^[^:]+:" line)
- (replace-match (format "\\&%s: " summary-status) t nil line)
- line))
- (update-comment
- (line)
- (if comment (format "COMMENT:%s" comment)
- line))
- (update-dtstamp ()
- (format-time-string "DTSTAMP:%Y%m%dT%H%M%SZ" nil t))
- (attendee-matches-identity
- (line)
- (gnus-icalendar-find-if (lambda (name) (string-match-p name line))
- identities))
- (update-attendee-status
- (line)
- (when (and (attendee-matches-identity line)
- (string-match "\\(PARTSTAT=\\)[^;]+" line))
- (replace-match (format "\\1%s" attendee-status) t nil line)))
- (process-event-line
- (line)
- (when (string-match "^\\([^;:]+\\)" line)
- (let* ((key (match-string 0 line))
- ;; NOTE: not all of the below fields are mandatory,
- ;; but they are often present in other clients'
- ;; replies. Can be helpful for debugging, too.
- (new-line
- (cond
- ((string= key "ATTENDEE") (update-attendee-status line))
- ((string= key "SUMMARY") (update-summary line))
- ((string= key "COMMENT") (update-comment line))
- ((string= key "DTSTAMP") (update-dtstamp))
- ((member key '("ORGANIZER" "DTSTART" "DTEND"
- "LOCATION" "DURATION" "SEQUENCE"
- "RECURRENCE-ID" "UID"))
- line)
- (t nil))))
- (when new-line
- (push new-line reply-event-lines))))))
-
- (mapc #'process-event-line (split-string ical-request "\n"))
-
- ;; RFC5546 refers to uninvited attendees as "party crashers".
- ;; This situation is common if the invitation is sent to a group
- ;; of people via a mailing list.
- (unless (gnus-icalendar-find-if (lambda (x) (string-match "^ATTENDEE" x))
- reply-event-lines)
- (lwarn 'gnus-icalendar :warning
- "Could not find an event attendee matching given identity")
- (push (format "ATTENDEE;RSVP=TRUE;PARTSTAT=%s;CN=%s:MAILTO:%s"
- attendee-status user-full-name user-mail-address)
- reply-event-lines))
-
- ;; add comment line if not existing
- (when (and comment
- (not (gnus-icalendar-find-if
- (lambda (x)
- (string-match "^COMMENT" x))
- reply-event-lines)))
- (push (format "COMMENT:%s" comment) reply-event-lines))
-
- (mapconcat #'identity `("BEGIN:VEVENT"
- ,@(nreverse reply-event-lines)
- "END:VEVENT")
- "\n"))))
-
-(defun gnus-icalendar-event-reply-from-buffer (buf status identities &optional comment)
+ recipient)
+ (ical:with-component vcalendar
+ ((ical:vtimezone :all tz-nodes)
+ (ical:vevent :first vevent))
+ (ical:with-component vevent
+ ((ical:summary :value summary)
+ (ical:attendee :all attendees)
+ (ical:uid :value uid)
+ (ical:comment :value old-comment)
+ ;; The nodes below are copied unchanged to the reply. Not all
+ ;; of them are mandatory, but they are often present in other
+ ;; clients' replies. Can be helpful for debugging, too.
+ (ical:organizer :first organizer-node)
+ (ical:dtstart :first dtstart-node)
+ (ical:dtend :first dtend-node)
+ (ical:duration :first duration-node)
+ (ical:location :first location-node)
+ (ical:sequence :first sequence-node)
+ (ical:recurrence-id :first recid-node))
+
+ (setq recipient (gnus-icalendar-event--find-attendee attendees ids))
+ (if recipient
+ (ical:with-property recipient
+ ((ical:partstatparam :first partstat-node))
+ (ical:ast-node-set-value partstat-node attendee-status))
+ ;; RFC5546 refers to uninvited attendees as "party crashers".
+ ;; This situation is common if the invitation is sent to a group
+ ;; of people via a mailing list.
+ (lwarn 'gnus-icalendar :warning
+ "Could not find a matching event attendee; creating new.")
+ (setq recipient
+ (ical:make-property ical:attendee
+ (concat "mailto:" user-mail-address)
+ (ical:partstatparam attendee-status)
+ (ical:cnparam user-full-name)))
+ (push recipient attendees))
+
+ ;; Build the reply:
+ (ical:make-vcalendar
+ (ical:method "REPLY")
+ (@ tz-nodes)
+ (ical:vevent
+ (ical:uid uid)
+ recid-node
+ sequence-node
+ organizer-node
+ dtstart-node
+ dtend-node
+ duration-node
+ location-node
+ (ical:summary
+ (if (string-match "^[^:]+:" summary)
+ (replace-match (format "\\&%s: " summary-status) t nil summary)
+ summary))
+ (ical:comment (or comment old-comment))
+ (@ attendees)))))))
+
+(defun gnus-icalendar-event-reply-from-buffer (buf status ids
+ &optional comment)
"Build a calendar event reply for request contained in BUF.
-The reply will have STATUS (`accepted', `tentative' or `declined').
-The reply will be composed for attendees matching any entry
-on the IDENTITIES list.
-Optional argument COMMENT will be placed in the comment field of the
-reply.
-"
- (cl-labels
- ((extract-block
- (blockname)
- (save-excursion
- (let ((block-start-re (format "^BEGIN:%s" blockname))
- (block-end-re (format "^END:%s" blockname))
- start)
- (when (re-search-forward block-start-re nil t)
- (setq start (line-beginning-position))
- (re-search-forward block-end-re)
- (buffer-substring-no-properties start (line-end-position)))))))
- (let (zone event)
- (with-current-buffer (icalendar--get-unfolded-buffer (get-buffer buf))
- (goto-char (point-min))
- (setq zone (extract-block "VTIMEZONE")
- event (extract-block "VEVENT")))
-
- (when event
- (let ((contents (list "BEGIN:VCALENDAR"
- "METHOD:REPLY"
- "PRODID:Gnus"
- "VERSION:2.0"
- zone
- (gnus-icalendar-event--build-reply-event-body event status identities comment)
- "END:VCALENDAR")))
-
- (mapconcat #'identity (delq nil contents) "\n"))))))
+The reply will have STATUS (`accepted', `tentative' or `declined'). The
+reply will be composed for attendees matching any entry in the
+IDS list. Optional argument COMMENT will be placed in the
+comment field of the reply."
+ (let (vcalendar reply)
+ (with-current-buffer (ical:unfolded-buffer-from-buffer (get-buffer buf))
+ (setq vcalendar (ical:parse))
+ (unless vcalendar
+ (error "Could not parse invitation; see buffer %s"
+ (buffer-name (ical:error-buffer))))
+ (setq reply
+ (gnus-icalendar-event--build-reply vcalendar status ids comment))
+ (ical:print-calendar-node reply))))
;;;
;;; gnus-icalendar-org
@@ -455,15 +375,17 @@ gnus-icalendar-event:org-repeat
"Return `org-mode' timestamp repeater string for recurring EVENT.
Return nil for non-recurring EVENT."
(when (gnus-icalendar-event:recurring-p event)
- (let* ((freq-map '(("HOURLY" . "h")
- ("DAILY" . "d")
- ("WEEKLY" . "w")
- ("MONTHLY" . "m")
- ("YEARLY" . "y")))
- (org-freq (cdr (assoc (gnus-icalendar-event:recurring-freq event) freq-map))))
+ (let* ((freq-map '((HOURLY . "h")
+ (DAILY . "d")
+ (WEEKLY . "w")
+ (MONTHLY . "m")
+ (YEARLY . "y")))
+ (org-freq
+ (alist-get (gnus-icalendar-event:recurring-freq event) freq-map))
+ (interval-size (gnus-icalendar-event:recurring-interval event)))
(when org-freq
- (format "+%s%s" (gnus-icalendar-event:recurring-interval event) org-freq)))))
+ (format "+%d%s" interval-size org-freq)))))
(defun gnus-icalendar--find-day (start-date end-date day)
(let ((time-1-day 86400))
@@ -1110,3 +1032,7 @@ gnus-icalendar-setup
(provide 'gnus-icalendar)
;;; gnus-icalendar.el ends here
+
+;; Local Variables:
+;; read-symbol-shorthands: (("ical:" . "icalendar-"))
+;; End:
diff --git a/test/lisp/gnus/gnus-icalendar-tests.el b/test/lisp/gnus/gnus-icalendar-tests.el
index 2658ca4848a..e7febda6fa5 100644
--- a/test/lisp/gnus/gnus-icalendar-tests.el
+++ b/test/lisp/gnus/gnus-icalendar-tests.el
@@ -35,7 +35,7 @@ gnus-icalendar-tests--get-ical-event
(let (event)
(with-temp-buffer
(insert ical-string)
- (setq event (gnus-icalendar-event-from-buffer (buffer-name) participant)))
+ (setq event (gnus-icalendar-event-from-buffer (current-buffer) participant)))
event))
(ert-deftest gnus-icalendar-parse ()
@@ -94,7 +94,8 @@ gnus-icalendar-parse
(setenv "TZ" "CET-1CEST,M3.5.0/2,M10.5.0/3")
(should (eq (eieio-object-class event) 'gnus-icalendar-event-request))
(should (not (gnus-icalendar-event:recurring-p event)))
- (should (string= (gnus-icalendar-event:start event) "2020-12-08 15:00"))
+ (should (equal (gnus-icalendar-event:start event)
+ "2020-12-08 15:00"))
(with-slots (organizer summary description location end-time uid rsvp participation-type) event
(should (string= organizer "anoncompany.com_3bm6fh805bme9uoeliqcle1sag@HIDDEN"))
(should (string= summary "Townhall | All Company Meeting"))
@@ -106,9 +107,20 @@ gnus-icalendar-parse
(should (eq participation-type 'non-participant))))
(setenv "TZ" tz))))
+(defun gnus-icalendar-at/@ ()
+ "Replace \" <at> \" with \"@\" before parsing."
+ (goto-char (point-min))
+ (while (re-search-forward " <at> " nil t)
+ (replace-match "@")))
+
+;; FIXME: is "icalendary" (not "icalendar") intentional, here and below?
(ert-deftest gnus-icalendary-byday ()
""
- (let ((tz (getenv "TZ"))
+ (let* ((tz (getenv "TZ"))
+ (icalendar-pre-parsing-hook
+ ;; clean up " <at> " addresses so the parser doesn't choke...
+ ;; FIXME: can we just change the test data, or is this a real example?
+ '(gnus-icalendar-at/@))
(event (gnus-icalendar-tests--get-ical-event "\
BEGIN:VCALENDAR
PRODID:Zimbra-Calendar-Provider
@@ -138,8 +150,8 @@ gnus-icalendary-byday
ATTENDEE;CN=Mark Hershberger;ROLE=REQ-PARTICIPANT;PARTSTAT=NEEDS-ACTION;RSVP
=TRUE:mailto:hexmode <at> gmail.com
ORGANIZER;CN=Mark A. Hershberger:mailto:mah <at> nichework.com
-DTSTART;TZID=\"America/New_York\":20200724T090000
-DTEND;TZID=\"America/New_York\":20200724T093000
+DTSTART;TZID=America/New_York:20200724T090000
+DTEND;TZID=America/New_York:20200724T093000
STATUS:CONFIRMED
CLASS:PUBLIC
X-MICROSOFT-CDO-INTENDEDSTATUS:BUSY
@@ -163,10 +175,12 @@ gnus-icalendary-byday
(setenv "TZ" "CET-1CEST,M3.5.0/2,M10.5.0/3")
(should (eq (eieio-object-class event) 'gnus-icalendar-event-request))
(should (gnus-icalendar-event:recurring-p event))
- (should (string= (gnus-icalendar-event:recurring-interval event) "1"))
+ (should (= 1 (gnus-icalendar-event:recurring-interval event)))
(should (string= (gnus-icalendar-event:start event) "2020-07-24 15:00"))
(with-slots (organizer summary description location end-time uid rsvp participation-type) event
- (should (string= organizer "mah <at> nichework.com"))
+ (should (string= organizer
+ (replace-regexp-in-string " <at> " "@"
+ "mah <at> nichework.com")))
(should (string= summary "appointment every weekday, start jul 24, 2020, end aug 24, 2020"))
(should (string= description "The following is a new meeting request:"))
(should (null location))
@@ -236,7 +250,7 @@ gnus-icalendary-weekly-byday
(setenv "TZ" "CET-1CEST,M3.5.0/2,M10.5.0/3")
(should (eq (eieio-object-class event) 'gnus-icalendar-event-request))
(should (gnus-icalendar-event:recurring-p event))
- (should (string= (gnus-icalendar-event:recurring-interval event) "1"))
+ (should (= 1 (gnus-icalendar-event:recurring-interval event)))
(should (string= (gnus-icalendar-event:start event) "2020-09-15 14:00"))
(with-slots (organizer summary description location end-time uid rsvp participation-type) event
(should (string= organizer "anon@HIDDEN"))
@@ -258,6 +272,29 @@ gnus-icalendary-weekly-byday
(ert-deftest gnus-icalendar-accept-with-comment ()
""
(let ((event "\
+BEGIN:VCALENDAR
+PRODID:-//Google Inc//Google Calendar 70.9054//EN
+VERSION:2.0
+CALSCALE:GREGORIAN
+METHOD:REQUEST
+BEGIN:VTIMEZONE
+TZID:Europe/Berlin
+X-LIC-LOCATION:Europe/Berlin
+BEGIN:DAYLIGHT
+TZOFFSETFROM:+0100
+TZOFFSETTO:+0200
+TZNAME:CEST
+DTSTART:19700329T020000
+RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU
+END:DAYLIGHT
+BEGIN:STANDARD
+TZOFFSETFROM:+0200
+TZOFFSETTO:+0100
+TZNAME:CET
+DTSTART:19701025T030000
+RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU
+END:STANDARD
+END:VTIMEZONE
BEGIN:VEVENT
DTSTART;TZID=Europe/Berlin:20200915T140000
DTEND;TZID=Europe/Berlin:20200915T143000
@@ -275,7 +312,8 @@ gnus-icalendar-accept-with-comment
STATUS:CONFIRMED
SUMMARY:Casual coffee talk
TRANSP:OPAQUE
-END:VEVENT")
+END:VEVENT
+END:VCALENDAR")
(icalendar-identities '("participant@HIDDEN")))
(let* ((reply (with-temp-buffer
(insert event)
@@ -292,6 +330,29 @@ gnus-icalendar-accept-with-comment
(ert-deftest gnus-icalendar-decline-without-changing-comment ()
""
(let ((event "\
+BEGIN:VCALENDAR
+PRODID:-//Google Inc//Google Calendar 70.9054//EN
+VERSION:2.0
+CALSCALE:GREGORIAN
+METHOD:REQUEST
+BEGIN:VTIMEZONE
+TZID:Europe/Berlin
+X-LIC-LOCATION:Europe/Berlin
+BEGIN:DAYLIGHT
+TZOFFSETFROM:+0100
+TZOFFSETTO:+0200
+TZNAME:CEST
+DTSTART:19700329T020000
+RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU
+END:DAYLIGHT
+BEGIN:STANDARD
+TZOFFSETFROM:+0200
+TZOFFSETTO:+0100
+TZNAME:CET
+DTSTART:19701025T030000
+RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU
+END:STANDARD
+END:VTIMEZONE
BEGIN:VEVENT
DTSTART;TZID=Europe/Berlin:20200915T140000
DTEND;TZID=Europe/Berlin:20200915T143000
@@ -310,7 +371,8 @@ gnus-icalendar-decline-without-changing-comment
STATUS:CONFIRMED
SUMMARY:Casual coffee talk
TRANSP:OPAQUE
-END:VEVENT")
+END:VEVENT
+END:VCALENDAR")
(icalendar-identities '("participant@HIDDEN")))
(let* ((reply (with-temp-buffer
(insert event)
--
2.39.5
--=-=-=--
Richard Lawrence <rwl@HIDDEN>:cohen@HIDDEN, eric@HIDDEN, yamaoka@HIDDEN, bug-gnu-emacs@HIDDEN.
Full text available.cohen@HIDDEN, eric@HIDDEN, yamaoka@HIDDEN, bug-gnu-emacs@HIDDEN:bug#80426; Package emacs.
Full text available.
GNU bug tracking system
Copyright (C) 1999 Darren O. Benham,
1997 nCipher Corporation Ltd,
1994-97 Ian Jackson.