Sun, 12 Feb 2006
I've thought for quite a while that I wanted to sort out my various TODO lists into something more general, and integrate it with a calendar of some sort.
My requirements didn't seem to extreme:
* access from any machine or some way to synchronise machines * calendar and TODO list integrated * let me know whats coming up and what I need to do soon
I looked at Chandler version 0.6.0, which is marked as "experimentally usable" but unfortunately I found it to be unusable. I see 0.6.1 is released now, so that might be better, but I've not tried it. But Chandler seemed to be overkill for what I wanted anyway.
So I started looking at command line applications. I experimented with ccal which seems quite nice. It's written in python and I even started hacking on it to make it do some more of what I wanted and some more of what it should have done, but in the end it just didn't do enough of what I wanted. Here's my patch, anyway:
--- /home/pjcj/utils/ccal06.py.org 2004-08-29 18:01:59.000000000 +0200
+++ /home/pjcj/utils/ccal 2006-02-12 17:38:08.230850964 +0100
@@ -182,11 +182,17 @@
print "ccal entries:\n"
if cal:
print "Calendar:\n"
- for item in self._cal.getItems():
- if not str(item.__class__)=="__main__.ccalItem":
- item=ccalItem(item)
-
- print item.entry
+ for i in range(999) :
+ viewtime = (datetime.datetime(self._cal.viewtime[0],self._cal.viewtime[1],self._cal.viewtime[2])+datetime.timedelta(days=i)).timetuple()
+ entries=self._cal.getItems(viewtime)
+ dateString = time.strftime(self._cal.dateformat,viewtime)
+ dateString += " ("+self.friendlyDateTimeDelta(datetime.datetime(viewtime[0], viewtime[1], viewtime[2]) - datetime.datetime(self._cal.localtime[0],self._cal.localtime[1],self._cal.localtime[2]))+")"
+ if entries!=None and len(entries) > 0:
+ entries.sort(lambda x, y: cmp(ccalItem(x).entry, ccalItem(y).entry))
+ for item in entries:
+ if not str(item.__class__)=="__main__.ccalItem":
+ item=ccalItem(item)
+ print dateString+": "+item.entry
print "\n"
if todo:
print "Todo list:\n"
@@ -523,6 +529,7 @@
ypos+=1
+ entries.sort(lambda x, y: cmp(ccalItem(x).entry, ccalItem(y).entry))
for entry in entries:
Then I started to take a look at some vim plugins hoping for better luck. I found a couple that individually seemed to do part of what I wanted. First there was VimOutliner, which will do nicely for managing my todos. Then there was calendar.vim, which will deal with the calendar part. Now all I needed to do was join them up so that todos with a date went into the calendar. I little bit of Perl sorted that out. So now I have the infrastructure. All I have to do now is use it.
#!/usr/bin/perl
# Copyright 2006, Paul Johnson (http://www.pjcj.net)
use warnings;
use strict;
use File::Find;
my $dir = <~/g/calendar>;
chdir $dir or die "Can't chdir $dir: $!";
my %todos;
my $outline = "pjcj.otl";
# Read the outline file and note any entries that have associateed dates.
open my $f, "<", $outline or die "Can't open $outline: $!";
while (<$f>)
{
# Dates have the format YYYY-MM-DD
push @{$todos{$2}{$1 eq "X" ? "done" : "open"}}, $3
if /^\s*(?:\[(.)\])?\s*(?:\d*%)?\s*(20\d\d-[01]\d-[0-3]\d)\s*(.+)/;
}
my ($y, $m, $d) = (localtime)[5, 4, 3];
my $today = sprintf "%04d-%02d-%02d", $y + 1900, $m + 1, $d;
print "Today is $today\n";
sub write_calendar
{
my ($cal) = @_;
# Get the date from the filename.
my ($y, $m, $d) = $cal =~ /\d+/g;
my $date = sprintf "%04d-%02d-%02d", $y, $m, $d;
my %entries;
if (-e $cal)
{
# Read in the calendar file, ignoring any existing todos.
open my $f, "<", $cal or die "Can't open $cal: $!";
my @l = grep /\S/ && !/^(?:TODO|DONE) /, <$f>;
chomp @l;
@entries{@l} = ();
# Delete the file - there may be nothing more to write to it.
unlink $cal or die "Can't delete $cal: $!";
}
# Add the todos from the outline.
@entries{map "TODO $_", @{$todos{$date}{open}}} = ();
@entries{map "DONE $_", @{$todos{$date}{done}}} = ();
{
# If there's nothing to be done, just get out.
last unless %entries;
# Write the new calendar file.
open my $f, ">", $cal or die "Can't open $cal: $!";
print $f "$_\n" for sort keys %entries;
# Print out what's coming up.
# Don't print if it's in the past and there are no todos,
# or if it's just todos and they are all done.
last if $date lt $today && !@{$todos{$date}{open}};
last if keys %entries eq @{$todos{$date}{done}};
print "$date\n";
print " $_\n" for sort keys %entries;
}
# We're finished with this date.
delete $todos{$date};
}
sub wanted
{
# We're only interested in calendar files.
return unless -f;
unlink, return if -z;
return unless /\.cal$/;
write_calendar $_;
}
{
no warnings "numeric";
# Go through the calendar files in chronological order.
find({
wanted => \&wanted,
preprocess => sub { sort { $a <=> $b } @_ },
no_chdir => 1
}, <20??>);
}
# Now take the remaining todos and turn them to the calendar files.
for my $date (sort keys %todos)
{
# Locate the calendar file from the date.
(my $cal = $date) =~ s|-0?|/|g;
$cal .= ".cal";
write_calendar $cal;
}