1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
|
#!/usr/bin/perl
use warnings;
use strict;
use feature qw(say);
use JSON;
use DateTime;
use DateTime::Format::ISO8601;
use Net::OpenSSH;
my @short_months = qw(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec);
# subs
sub parse_scheduled
{
my $sched_date = shift;
return DateTime::Format::ISO8601->parse_datetime($sched_date);
}
# ALGORITHM
# Parse the scheduled attribute from TW
my %token_regexes = (
tdelta => qr/\+(\d+)/, # +INT (see remind man page)
trepeat => qr/\*(\d+)/, # *INT (see remind man page)
);
my $added_task = <STDIN>;
my $work_rem_file = '~/.reminders/work.rem';
my $decoded_task = decode_json $added_task;
my $original_description = ${$decoded_task}{description};
my $tdelta;
my $trepeat;
if (($original_description =~ m/$token_regexes{tdelta}/g)) {
$tdelta = "+$1"; # corresponds to tdelta in remind: how many minutes prior to reminder it reminds
$original_description =~ s/$token_regexes{tdelta}//g; # remove the delta time token
} else {
$tdelta = "";
};
if (($original_description =~ m/$token_regexes{trepeat}/g)) {
if ($tdelta eq "") { die "Cannot have a repeat token without a delta token" };
$trepeat = "*$1"; # corresponds to trepeat in remind: how many minutes within tdelta it pings repeatedly
$original_description =~ s/$token_regexes{trepeat}//g; # remove the delta time token
} else {
$trepeat = "";
};
my $tags = ${$decoded_task}{tags}; # alternative - not using -> in the ref
my $scheduled_dt;
if ($decoded_task->{scheduled} and (scalar grep {$_ eq "dft" } @{$tags})) {
$scheduled_dt = parse_scheduled $decoded_task->{scheduled};
my $port = 22;
my $date = $scheduled_dt->day();
my $month = $short_months[$scheduled_dt->month()-1];
my $year = $scheduled_dt->year();
my $hr = $scheduled_dt->hour();
my $min = $scheduled_dt->minute();
my $time = substr $scheduled_dt->hms(), 0, 5; # we do not want seconds in the time format
# Convert it into Remind format with %" bits that mean you don't get the
# shit in wyrd
my $remind_line = "REM $date $month $year AT $time $tdelta $trepeat MSG \%\"$original_description\%\" \%b\n";
$remind_line =~ s/ +/ /g;
# Log into remote server
my $host = $ENV{"TW_HOOK_REMIND_REMOTE_HOST"} or die "Cannot get TW_HOOK_REMIND_REMOTE_HOST environment variable";
my $user = $ENV{"TW_HOOK_REMIND_REMOTE_USER"} or die "Cannot get TW_HOOK_REMIND_REMOTE_USER environment variable";
# use correct port
if ($host =~ m/.*\.xyz$/) { $port = 2222 };
say "Trying to establish connection at $host:$port ...";
my $ssh = Net::OpenSSH->new($host, user => $user, port => $port);
$ssh->error and die "Couldn't establish SSH connection: " . $ssh->error;
# Check for presence or remind file
if ($ssh->test("ls $work_rem_file") != 1) { die "Cannot find $work_rem_file on $host."};
# If it is there, back it up
$ssh->system("cp $work_rem_file $work_rem_file.bak") or die "Cannot create a back-up of remind file.";
# Append the Remind formatted line to the original remind file
$ssh->system({stdin_data => $remind_line}, "cat >> $work_rem_file") or die "Cannot append text: " . $ssh->error;
# Get content of remind file
my @out_file = $ssh->capture("cat $work_rem_file");
print qq/
Contents of $work_rem_file on $host is now:\n/,
@out_file;
# TODO - we need to strip away the %:MIN syntax from the original
# description - need to substitute it here!
$decoded_task->{description} = $original_description;
print encode_json $decoded_task;
exit 0;
} else {
print $added_task;
print("Add hook not used.\n");
exit 0;
}
=pod
=head1 NAME
on-add_scheduled_work_task
=head1 DESCRIPTION
This is a Taskwarrior hook for interacting with the remind calendar on a remote server. It currently only
works under a specific set of circumstances which will be explained here.
The current implementation will add a remind item for a taskwarrior item which has the tag "dft" and is "scheduled"
for a time and date.
=head1 PREREQUISITES
=over
=item * A remote server and its IP address or domain name with remind already set up, and ssh access to it.
=item * Taskwarrior - with this perl script at ~/.task/hooks/on-add_scheduled_work_task.pl
=item * An environment variable TW_HOOK_REMIND_REMOTE_HOST set with the IP address or domain name of the remote server which hosts remind.
=item * An environment variable TW_HOOK_REMIND_REMOTE_USER set with the username on the remote server which ssh requires to log in.
=item * The following perl dependences: JSON, Net::OpenSSH, DateTime and DateTime::Format::ISO8601 installed.
=back
=head1 REMIND SYNTAX
C<REM [ONCE] [date_spec] [back] [delta] [repeat] [PRIORITY prio] [SKIP | BEFORE | AFTER] [OMIT omit_list] [OMITFUNC omit_function] [AT time
[tdelta] [trepeat]] [SCHED sched_function] [WARN warn_function] [UNTIL expiry_date] [SCANFROM scan_date | FROM start_date] [DURATION
duration] [TAG tag] E<lt>MSG | MSF | RUN | CAL | SATISFY | SPECIAL special | PS | PSFILEE<gt> body>
The elements we are interested in are:
=over
=item * delta (for advanced warning of the date)
=item * repeat (for repeating from the trigger date)
=item * tdelta (for advanced warning of the AT time)
=item * trepeat (for repeating the advanced reminder)
=back
=head2 Advance warning (delta)
For some reminders, it is appropriate to receive advance warning of the event. For example, you may wish to be reminded of someone's birthday
several days in advance. The delta portion of the REM command achieves this. It is specified as one or two "+" signs followed by a number n.
Again, the difference between the "+" and "++" forms will be explained under the OMIT keyword. Remind will trigger the reminder on computed
trigger date, as well as on each of the n days before the event. Here are some examples:
C<REM 6 Jan +5 MSG Remind me of birthday 5 days in advance.>
The above example would be triggered every 6th of January, as well as the 1st through 5th of January.
=head2 Recurring events (repeat)
However, events that do not repeat daily, weekly, monthly or yearly require another approach. The repeat component of the REM command fills this
need. To use it, you must completely specify a date (year, month and day, and optionally weekday.) The repeat component is an asterisk
followed by a number specifying the repetition period in days.
For example, suppose you get paid every second Wednesday, and your last payday was Wednesday, 28 October, 1992. You can use:
C<REM 28 Oct 1992 *14 MSG Payday>
This issues the reminder every 14 days, starting from the calculated trigger date. You can use delta and back with repeat. Note, however, that the
back is used only to compute the initial trigger date; thereafter, the reminder repeats with the specified period. Similarly, if you specify
a weekday, it is used only to calculate the initial date, and does not affect the repetition period.
=head1 REQUIRED TASKWARRIOR FORMAT
The hook is only triggered when a new task is added with a "dft" tag and is "scheduled".
=head2 Example using tdelta and trepeat (a remind command with AT/timed element)
The syntax for tdelta and trepeat must be included in the task description. It matches the equivalent remind syntax (+10 and *1).
These are removed from the description before saving and are used in the C<AT> clause in remind.
=over
=item
C<task add Meaningless meeting +10 *1 +dft scheduled:2021-10-09T10:00Z>
=back
The C<Z> is optional, but the time specified is in Zulu time, so take that into account. When in BST it will take an hour off.
Although this is a meaningless meeting, it is important enough to be reminded of it 10 minutes before 10am (C<+10>), with a repeat
every minute (C<*1>) between the initial reminder and the time of the meeting itself.
The additional C<tdelta> and C<trepeat> tags (+10 and *1) are removed from the task description before either getting to remind
or to taskwarrior.
=head2 Example using delta (a remind command with advanced warning in days)
The only way that delta is different from tdelta inside the remind REM command is from it's placement: delta relates to the date aspect
whereas tdelta relates to time in the C<AT> clause. We wish to retain the use of "+" but we must distinguise it inside the task description
from tdelta so for delta we prefix with C<D>: e.g. C<D+10> which says that this must give us advance warning of 10 days. At this point, we
are only using one C<+>, not two because use of the C<OMIT> keyword is not yet implemented.
=item
C<task add Meaningless meeting D+2 +dft scheduled:2021-10-09T10:00Z>
=back
This will pre-warn us 2 days in advance of the meaningless meeting scheduled to take place on 9 October 2021 at 11:00BST.
TODO
=head2 Example using repeat (a remind command which creates a repeating event)
TODO
=cut
|