]> git.donarmstrong.com Git - bin.git/commitdiff
add ical2rem ical->reminder converter
authorDon Armstrong <don@donarmstrong.com>
Sat, 11 Aug 2007 01:01:40 +0000 (01:01 +0000)
committerDon Armstrong <don@donarmstrong.com>
Sat, 11 Aug 2007 01:01:40 +0000 (01:01 +0000)
ical2rem [new file with mode: 0755]

diff --git a/ical2rem b/ical2rem
new file mode 100755 (executable)
index 0000000..aab07d5
--- /dev/null
+++ b/ical2rem
@@ -0,0 +1,279 @@
+#!/usr/bin/perl -w
+#
+# ical2rem.pl - 
+# Reads iCal files and outputs remind-compatible files.   Tested ONLY with
+#   calendar files created by Mozilla Calendar/Sunbird. Use at your own risk.
+# Copyright (c) 2005, 2007, Justin B. Alcorn
+
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+# 
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+# 
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+#
+#
+# version 0.5.2 2007-03-23
+#      - BUG: leadtime for recurring events had a max of 4 instead of DEFAULT_LEAD_TIME
+#      - remove project-lead-time, since Category was a non-standard attribute
+#      - NOTE: There is a bug in iCal::Parser v1.14 that causes multiple calendars to
+#              fail if a calendar with recurring events is followed by a calendar with no
+#              recurring events.  This has been reported to the iCal::Parser author.
+# version 0.5.1 2007-03-21
+#      - BUG: Handle multiple calendars on STDIN
+#      - add --heading option for priority on section headers
+# version 0.5 2007-03-21
+#      - Add more help options
+#      - --project-lead-time option
+#      - Supress printing of heading if there are no todos to print
+# version 0.4
+#      - Version 0.4 changes all written or inspired by, and thanks to Mark Stosberg
+#      - Change to GetOptions
+#      - Change to pipe
+#      - Add --label, --help options
+#      - Add Help Text
+#      - Change to subroutines
+#      - Efficiency and Cleanup
+# version 0.3
+#      - Convert to GPL (Thanks to Mark Stosberg)
+#      - Add usage
+# version 0.2
+#      - add command line switches
+#      - add debug code
+#      - add SCHED _sfun keyword
+#      - fix typos
+# version 0.1 - ALPHA CODE.  
+
+=head1 SYNOPSIS
+
+ cat /path/to/file*.ics | ical2rem.pl > ~/.ical2rem
+
+ All options have reasonable defaults:
+ --label                      Calendar name (Default: Calendar)
+ --lead-time          Advance days to start reminders (Default: 3)
+ --todos, --no-todos   Process Todos? (Default: Yes)
+ --heading                        Define a priority for static entries
+ --help                                   Usage
+ --man                            Complete man page
+
+Expects an ICAL stream on STDIN. Converts it to the format
+used by the C<remind> script and prints it to STDOUT. 
+
+=head2 --label
+
+  ical2rem.pl --label "Bob's Calendar"
+
+The syntax generated includes a label for the calendar parsed.
+By default this is "Calendar". You can customize this with 
+the "--label" option.
+
+=head2 --lead-time 
+
+  ical2rem.pl --lead-time 3
+How may days in advance to start getting reminders about the events. Defaults to 3. 
+
+=head2 --no-todos
+
+  ical2rem.pl --no-todos
+
+If you don't care about the ToDos the calendar, this will surpress
+printing of the ToDo heading, as well as skipping ToDo processing. 
+
+=head2 --heading
+
+  ical2rem.pl --heading "PRIORITY 9999"
+
+Set an option on static messages output.  Using priorities can made the static messages look different from
+the calendar entries.  See the file defs.rem from the remind distribution for more information.
+
+=cut 
+
+use strict;
+use iCal::Parser;
+use DateTime;
+use Getopt::Long 2.24 qw':config auto_help';
+use Pod::Usage;
+use Data::Dumper;
+use vars '$VERSION';
+$VERSION = "0.5.2";
+
+# Declare how many days in advance to remind
+my $DEFAULT_LEAD_TIME = 3;
+my $PROCESS_TODOS     = 1;
+my $HEADING                      = "";
+my $help;
+my $man;
+
+my $label = 'Calendar';
+GetOptions (
+       "label=s"     => \$label,
+       "lead-time=i" => \$DEFAULT_LEAD_TIME,
+       "todos!"          => \$PROCESS_TODOS,
+       "heading=s"       => \$HEADING,
+       "help|?"          => \$help, 
+       "man"             => \$man
+);
+pod2usage(1) if $help;
+pod2usage(-verbose => 2) if $man;
+
+my $month = ['None','Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'];
+
+my @calendars;
+my $in;
+
+while (<>) {
+       $in .= $_;
+       if (/END:VCALENDAR/) {
+               push(@calendars,$in);
+               $in = "";
+       }
+}
+my $parser = iCal::Parser->new();
+my $hash = $parser->parse_strings(@calendars);
+
+##############################################################
+#
+# Subroutines 
+#
+#############################################################
+#
+# _process_todos()
+# expects 'todos' hashref from iCal::Parser is input
+# returns String to output
+sub _process_todos {
+       my $todos = shift; 
+       
+       my ($todo, @newtodos, $leadtime);
+       my $output = "";
+
+       $output .=  'REM '.$HEADING.' MSG '.$label.' ToDos:%"%"%'."\n";
+
+# For sorting, make sure everything's got something
+#   To sort on.  
+       my $now = DateTime->now;
+       for $todo (@{$todos}) {
+               # remove completed items
+               if ($todo->{'STATUS'} && $todo->{'STATUS'} eq 'COMPLETED') {
+                       next;
+               } elsif ($todo->{'DUE'}) {
+                       # All we need is a due date, everything else is sugar
+                       $todo->{'SORT'} = $todo->{'DUE'}->clone;
+               } elsif ($todo->{'DTSTART'}) {
+                       # for sorting, sort on start date if there's no due date
+                       $todo->{'SORT'} = $todo->{'DTSTART'}->clone;
+               } else {
+                       # if there's no due or start date, just make it now.
+                       $todo->{'SORT'} = $now;
+               }
+               push(@newtodos,$todo);
+       }
+       if (! (scalar @newtodos)) {
+               return "";
+       }
+# Now sort on the new Due dates and print them out.  
+       for $todo (sort { DateTime->compare($a->{'SORT'}, $b->{'SORT'}) } @newtodos) {
+               my $due = $todo->{'SORT'}->clone();
+               my $priority = "";
+               if (defined($todo->{'PRIORITY'})) {
+                       if ($todo->{'PRIORITY'} == 1) {
+                               $priority = "PRIORITY 1000";
+                       } elsif ($todo->{'PRIORITY'} == 3) {
+                               $priority = "PRIORITY 7500";
+                       }
+               }
+               if (defined($todo->{'DTSTART'}) && defined($todo->{'DUE'})) {
+                       # Lead time is duration of task + lead time
+                       my $diff = ($todo->{'DUE'}->delta_days($todo->{'DTSTART'})->days())+$DEFAULT_LEAD_TIME;
+                       $leadtime = "+".$diff;
+               } else {
+                       $leadtime = "+".$DEFAULT_LEAD_TIME;
+               }
+               $output .=  "REM ".$due->month_abbr." ".$due->day." ".$due->year." $leadtime $priority MSG \%a $todo->{'SUMMARY'}\%\"\%\"\%\n";
+       }
+       $output .= 'REM '.$HEADING.' MSG %"%"%'."\n";
+       return $output;
+}
+
+
+#######################################################################
+#
+#  Main Program
+#
+######################################################################
+
+print _process_todos($hash->{'todos'}) if $PROCESS_TODOS;
+
+my ($leadtime, $yearkey, $monkey, $daykey,$uid,%eventsbyuid);
+print 'REM '.$HEADING.' MSG '.$label.' Events:%"%"%'."\n";
+my $events = $hash->{'events'};
+foreach $yearkey (sort keys %{$events} ) {
+    my $yearevents = $events->{$yearkey};
+    foreach $monkey (sort {$a <=> $b} keys %{$yearevents}){
+        my $monevents = $yearevents->{$monkey};
+        foreach $daykey (sort {$a <=> $b} keys %{$monevents} ) {
+            my $dayevents = $monevents->{$daykey};
+            foreach $uid (sort {
+                            DateTime->compare($dayevents->{$a}->{'DTSTART'}, $dayevents->{$b}->{'DTSTART'})    
+                            } keys %{$dayevents}) {
+                my $event = $dayevents->{$uid};
+               if ($eventsbyuid{$uid}) {
+                    my $curreventday = $event->{'DTSTART'}->clone;
+                    $curreventday->truncate( to => 'day' );
+                    $eventsbyuid{$uid}{$curreventday->epoch()} =1;
+                    for (my $i = 0;$i < $DEFAULT_LEAD_TIME && !defined($event->{'LEADTIME'});$i++) {
+                        if ($eventsbyuid{$uid}{$curreventday->subtract( days => $i+1 )->epoch() }) {
+                            $event->{'LEADTIME'} = $i;
+                        }
+                    }
+                } else {
+                    $eventsbyuid{$uid} = $event;
+                    my $curreventday = $event->{'DTSTART'}->clone;
+                    $curreventday->truncate( to => 'day' );
+                    $eventsbyuid{$uid}{$curreventday->epoch()} =1;
+                }
+
+            }
+        }
+    }
+}
+foreach $yearkey (sort keys %{$events} ) {
+    my $yearevents = $events->{$yearkey};
+    foreach $monkey (sort {$a <=> $b} keys %{$yearevents}){
+        my $monevents = $yearevents->{$monkey};
+        foreach $daykey (sort {$a <=> $b} keys %{$monevents} ) {
+            my $dayevents = $monevents->{$daykey};
+            foreach $uid (sort {
+                            DateTime->compare($dayevents->{$a}->{'DTSTART'}, $dayevents->{$b}->{'DTSTART'})
+                            } keys %{$dayevents}) {
+                my $event = $dayevents->{$uid};
+                if (exists($event->{'LEADTIME'})) {
+                    $leadtime = "+".$event->{'LEADTIME'};
+                } else {
+                    $leadtime = "+".$DEFAULT_LEAD_TIME;
+                }
+                my $start = $event->{'DTSTART'};
+                print "REM ".$start->month_abbr." ".$start->day." ".$start->year." $leadtime ";
+                if ($start->hour > 0) { 
+                    print " AT ";
+                    print $start->strftime("%H:%M");
+                    print " SCHED _sfun MSG %a %2 ";
+                } else {
+                    print " MSG %a ";
+                }
+                print "%\"$event->{'SUMMARY'}";
+                print " at $event->{'LOCATION'}" if $event->{'LOCATION'};
+                print "\%\"%\n";
+            }
+        }
+    }
+}
+exit 0;
+#:vim set ft=perl ts=4 sts=4 expandtab :