From a071bf9a56241aa743e1be21bccbb4cb72b189f2 Mon Sep 17 00:00:00 2001 From: Calvin Morrison Date: Sat, 22 Aug 2026 16:48:07 -0400 Subject: pim: invite, and a way to say what you mean pim/invite makes an event and, when it names attendees, sends the invitation -- which on a caldav server with auto-schedule is the same act. -p writes the object to standard output instead, so it can go to upas/marshal as iMIP, into a directory an ical/fs is serving, or nowhere in particular. There is no separate verb for inviting because to a calendar there is no separate thing. It writes the tree's own key: value form rather than icalendar. The first version spelled SUMMARY: by hand and got "lunch, then a walk" wrong: a comma is a separator there and wants escaping. Parsing with go-ical and generating by hand is the wrong asymmetry, so calfs grew Compose, and now the same shape the tree emits is the shape it accepts. The one UID we ever mint -- as organiser we own the event -- is minted there too. pim/invites lists what is waiting for an answer, which is what makes pim/rsvp usable: it walks the date tree rather than every event, since an invitation you never answered last March is not news. agenda and show take -d date now, via seconds(1), which parses a human date where date(1) only formats one. Co-Authored-By: Claude Opus 5 --- pim/rc/invite | 137 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 137 insertions(+) create mode 100755 pim/rc/invite (limited to 'pim/rc/invite') diff --git a/pim/rc/invite b/pim/rc/invite new file mode 100755 index 0000000..cf2e69c --- /dev/null +++ b/pim/rc/invite @@ -0,0 +1,137 @@ +#!/bin/rc +# pim/invite -- make an event, and invite people to it. +# +# pim/invite -c fastmail 'catch up' '2026-08-28 15:00' 30 +# pim/invite -c fastmail -a bob@example.com -l 'the wire' \ +# 'design review' 'tomorrow 14:00' 60 +# +# What this really does is write an icalendar object. Where it goes is +# somebody else's business: +# +# -p writes it to standard output, so you can mail it yourself +# (upas/marshal -t 'text/calendar; method=REQUEST'), keep it, +# or drop it in a directory some ical/fs is serving +# otherwise it goes to the calendar's new file, and a backend with +# calendar-auto-schedule mails every attendee for you +# +# So there is no separate verb for "invite": to a calendar there is no +# separate thing, and to everything else it is just a file. +rfork e + +mtpt=/mnt/pim +print=() +cal=() +att=() +loc=() +desc=() + +while(~ $1 -*){ + switch($1){ + case -m + mtpt=$2; shift + case -c + cal=$2; shift + case -a + att=($att $2); shift + case -l + loc=$2; shift + case -D + desc=$2; shift + case -p + print=1 + case * + echo 'usage: invite [-m mtpt] [-c cal] [-a attendee]... [-l where] [-D text] [-p] summary start [minutes]' >[1=2] + exit usage + } + shift +} +if(test $#* -lt 2){ + echo 'usage: invite [-m mtpt] [-c cal] [-a attendee]... [-l where] [-D text] summary start [minutes]' >[1=2] + exit usage +} +summary=$1 +when=$2 +mins=30 +if(! ~ $#* 2) + mins=$3 + +# with -p the object goes to standard output and no calendar is needed +if(! ~ $#print 0){ + if(~ $#cal 0) + cal=`{pim/calendars -m $mtpt | sed 1q} +} +if(~ $#print 0 && ~ $#cal 0){ + for(c in `{pim/calendars -m $mtpt}){ + caps=`{sed -n 's/^caps //p' $mtpt/calendars/$c/ctl >[2]/dev/null} + for(x in $caps) + if(~ $x write) + cal=($cal $c) + } + if(~ $#cal 0){ + echo 'pim/invite: no calendar here can create events' >[1=2] + exit nowhere + } + if(! ~ $#cal 1){ + echo 'pim/invite: which calendar? -c one of: '^$"cal >[1=2] + exit ambiguous + } +} +d=$mtpt/calendars/$"cal +if(~ $#print 0) + if(! test -f $d/new){ + echo 'pim/invite: '^$"cal^' cannot create events' >[1=2] + exit readonly + } + +start=`{seconds $"when} +if(~ $#start 0){ + echo 'pim/invite: cannot read the time '^$"when >[1=2] + exit baddate +} +# organiser is whoever this calendar says we are +me=`{sed -n 's/^me //p' $d/ctl >[2]/dev/null | sed 's/,.*//'} +if(~ $#att 0) + me=() +if(! ~ $#att 0) + if(~ $#me 0){ + echo 'pim/invite: '^$"cal^' has no me=; cannot say who is organising' >[1=2] + exit nome + } + +# The event is written as key: value, the same form the tree emits and +# calfs composes from. A script spelling SUMMARY: by hand has to know +# that a comma means something there, and will eventually forget; this +# way go-ical does the spelling and the escaping. +end=`{echo $"start + $"mins '*' 60 | bc} + +tmp=/tmp/invite.$pid +fn sigexit { rm -f $tmp } +{ + echo 'summary: '^$"summary + echo 'start: '^$"start + echo 'end: '^$"end + if(! ~ $#loc 0) + echo 'location: '^$"loc + if(! ~ $#desc 0) + echo 'description: '^$"desc + if(! ~ $#att 0){ + echo 'organizer: '^$"me + for(a in $att) + echo 'attendee: '^$a + } +} >$tmp + +if(! ~ $#print 0){ + cat $tmp + exit 0 +} +cat $tmp >$d/new +if(! ~ $status ''){ + echo 'pim/invite: '^$"status >[1=2] + exit failed +} +w=`{date -f 'WWW DD MMM hh:mm' $"start} +echo $"summary^': '^$"w^' on '^$"cal +if(! ~ $#att 0) + echo 'invited: '^$"att +exit 0 -- cgit v1.2.3