ICal2Rem

From 43FoldersWiki

(Difference between revisions)
Jump to: navigation, search
Revision as of 18:40, 18 March 2007 (edit)
MarkStosberg (Talk | contribs)
(a version of ical2rem with several new features: --help, --no-todos, --lead-time)
← Previous diff
Revision as of 16:32, 21 March 2007 (edit) (undo)
Jalcorn (Talk | contribs)
(Relicensed code under GPL)
Next diff →
Line 5: Line 5:
This version is modified from the one on the [http://jalcorn.net/weblog/archives/899-iCal-to-Remind-script.html author's site] to include the following changes: It expects input through a pipe, and has an optional "--label" option This version is modified from the one on the [http://jalcorn.net/weblog/archives/899-iCal-to-Remind-script.html author's site] to include the following changes: It expects input through a pipe, and has an optional "--label" option
-if you want to give the refer the calendar with a more descriptive label than "Calendar". +if you want to give the refer the calendar with a more descriptive label than "Calendar".
 + 
 +A Subversion repository will be available soon for "official" versions. Version 0.4, as of 2007-03-21, is available here and
 +[http://jalcorn.net/public/ical2rem.pl at the author's site].
Example uses: Example uses:
Line 23: Line 26:
=== Installation === === Installation ===
-You'll need to copy the following script and save to a file named "ical2rem" which should be placed+Download the script from [http://jalcorn.net/public/ical2rem.pl The Author's Site] or
 +copy the following script and save to a file named "ical2rem" which should be placed
in a "bin" directory in your executable path. <code>/usr/local/bin/</code> or <code>~/bin</code> are good in a "bin" directory in your executable path. <code>/usr/local/bin/</code> or <code>~/bin</code> are good
candidates. You may need to install some Perl modules first. Try <code>cpan -i iCal::Parser DateTime</code> candidates. You may need to install some Perl modules first. Try <code>cpan -i iCal::Parser DateTime</code>
Line 33: Line 37:
=== License === === License ===
-If this code were made available under the same open source license as 'remind', it would be easier to distribute+The code for ical2rem.pl has now been released under the GPL.
-the script with remind for Linux distrubitions to provide ready-made packages for it. Please consider+ 
-[http://jalcorn.net/weblog/archives/899-iCal-to-Remind-script.html leaving a comment for the author] asking him to re-license the software the GPL. As the only other contributor, I (Mark Stosberg) consider my updates available under the GPL already. +=== Author ===
 +The code was originally created by Justin B. Alcorn, inspired by a message thread on the 43Folders board discussing
 +conversion scripts written in Applescript.
 +Please consider [http://jalcorn.net/weblog/archives/899-iCal-to-Remind-script.html leaving a comment for the author]
<pre> <pre>
#!/usr/bin/perl -w #!/usr/bin/perl -w
# #
-#Copyright (c) 2005, Justin B. Alcorn+# ical2rem.pl -
-#All rights reserved.+
-#+
-#Redistribution and use in source and binary forms, with or without+
-#modification, are permitted provided that the following conditions are met:+
-#+
-#1) Redistributions of source code must retain the above copyright notice,+
-# this list of conditions and the following disclaimer.+
-#+
-#2) Redistributions in binary form must reproduce the above copyright notice,+
-# this list of conditions and the following disclaimer in the documentation+
-# and/or other materials provided with the distribution.+
-#+
-#3) Neither the name of Justin B. Alcorn nor the names of his+
-# contributors may be used to endorse or promote products derived from this+
-# software without specific prior written permission.+
-#+
-#THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"+
-#AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE+
-#IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE+
-#ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE+
-#LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR+
-#CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF+
-#SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS+
-#INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN+
-#CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)+
-#ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE+
-#POSSIBILITY OF SUCH DAMAGE.+
-#+
# Reads iCal files and outputs remind-compatible files. Tested ONLY with # Reads iCal files and outputs remind-compatible files. Tested ONLY with
# calendar files created by Mozilla Calendar/Sunbird. Use at your own risk. # 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.
# #
-# Changes: +# This program is distributed in the hope that it will be useful,
-# 0.3 03/18/07, by Mark Stosberg +# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# - Add --lead-time option+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# - Add --help option+# GNU General Public License for more details.
-# - Add --todos and --no-todos options+
-# - Fix warning with empty summaries+
-# 0.2 03/17/07, by Mark Stosberg +
-# - Updated to expect ics on STDIN+
-# - Added support for --label option to change name of calendar+
# #
-# 0.1 - ALPHA CODE. +# 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.4 2007-03-21
 +# - 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 =head1 SYNOPSIS
- cat /path/to/file.ics | ical2rem >~/.icalrem+ cat /path/to/file*.ics | ical2rem.pl > ~/.ical2rem
- --label Calendar name (Default: Calendar)+ --label Calendar name (Default: Calendar)
- --lead-time Advance days to start reminders (Default: 3)+ --lead-time Advance days to start reminders (Default: 3)
--todos, --no-todos Process Todos? (Default: Yes) --todos, --no-todos Process Todos? (Default: Yes)
Expects an ICAL stream on STDIN. Converts it to the format Expects an ICAL stream on STDIN. Converts it to the format
-used by the C<remind> script and prints it to STDOUT. +used by the C<remind> script and prints it to STDOUT.
=head2 --label =head2 --label
Line 100: Line 101:
The syntax generated includes a label for the calendar parsed. The syntax generated includes a label for the calendar parsed.
-By default this is "Calendar". You can customize this with +By default this is "Calendar". You can customize this with
the "--label" option. the "--label" option.
-=head2 --lead-time +=head2 --lead-time
ical2rem --lead-time 3 ical2rem --lead-time 3
- + 
-How may days in advance to start getting reminders about the events. Defaults to 3. +How may days in advance to start getting reminders about the events. Defaults to 3.
=head2 --no-todos =head2 --no-todos
Line 114: Line 115:
If you don't care about the ToDos the calendar, this will surpress If you don't care about the ToDos the calendar, this will surpress
-printing of the ToDo heading, as well as skipping ToDo processing. +printing of the ToDo heading, as well as skipping ToDo processing.
-=cut +=cut
use strict; use strict;
use iCal::Parser; use iCal::Parser;
-use Data::Dumper; 
-use Time::Local; 
use DateTime; use DateTime;
use Getopt::Long 2.24 qw':config auto_help'; use Getopt::Long 2.24 qw':config auto_help';
use vars '$VERSION'; use vars '$VERSION';
-$VERSION = 0.3;+$VERSION = 0.4;
-# declare how many days in advance to remind+# Declare how many days in advance to remind
-my $DEFAULT_LEAD_TIME = 3; +my $DEFAULT_LEAD_TIME = 3;
my $PROJECT_LEAD_TIME = 14; # used with Category is 'Projects' my $PROJECT_LEAD_TIME = 14; # used with Category is 'Projects'
my $PROCESS_TODOS = 1; my $PROCESS_TODOS = 1;
Line 134: Line 133:
my $label = 'Calendar'; my $label = 'Calendar';
GetOptions ( GetOptions (
- "label=s" => \$label,+ "label=s" => \$label,
- "lead-time=i" => \$DEFAULT_LEAD_TIME,+ "lead-time=i" => \$DEFAULT_LEAD_TIME,
- "todos!" => \$PROCESS_TODOS,+ "todos!" => \$PROCESS_TODOS,
-); +);
my $month = ['None','Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec']; my $month = ['None','Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'];
- 
my $hash = iCal::Parser->new->parse(\*STDIN); my $hash = iCal::Parser->new->parse(\*STDIN);
-my ($key, $todo, @newtodos, $leadtime);+##############################################################
 +#
 +# Subroutines
 +#
 +#############################################################
 +#
 +# _process_todos()
 +# expects 'todos' hashref from iCal::Parser is input
 +# returns String to output
 +sub _process_todos {
 + my $todos = shift;
-_process_todos($hash->{'todos'}) if $PROCESS_TODOS;+ my ($todo, @newtodos, $leadtime);
 + my $output = "";
-my ($yearkey, $monkey, $daykey,$uid,%eventsbyuid);+ $output .= 'REM PRIORITY 9999 MSG '.$label.' ToDos:%"%"%'."\n";
-print 'REM MSG %"%"%'."\n";+ 
-print 'MSG '.$label.' Events:%"%"%'."\n";+# For sorting, make sure everything's got something
-my $events = $hash->{'events'};+# To sort on.
-for $yearkey (sort keys %{$events} ) {+ my $now = DateTime->now;
- my $yearevents = $events->{$yearkey};+ for $todo (@{$todos}) {
- for $monkey (sort {$a <=> $b} keys %{$yearevents}){+ # remove completed items
- my $monevents = $yearevents->{$monkey};+ if ($todo->{'STATUS'} && $todo->{'STATUS'} eq 'COMPLETED') {
- for $daykey (sort {$a <=> $b} keys %{$monevents} ) {+ next;
- my $dayevents = $monevents->{$daykey};+ } elsif ($todo->{'DUE'}) {
- for $uid (sort {+ # All we need is a due date, everything else is sugar
- DateTime->compare($dayevents->{$a}->{'DTSTART'}, $dayevents->{$b}->{'DTSTART'}) + $todo->{'SORT'} = $todo->{'DUE'}->clone;
- } keys %{$dayevents}) {+ } elsif ($todo->{'DTSTART'}) {
- my $event = $dayevents->{$uid};+ # for sorting, sort on start date if there's no due date
- if ($eventsbyuid{$uid}) {+ $todo->{'SORT'} = $todo->{'DTSTART'}->clone;
- my $curreventday = $event->{'DTSTART'}->clone;+
- $curreventday->truncate( to => 'day' );+
- $eventsbyuid{$uid}{$curreventday->epoch()} =1;+
- for (my $i = 0;$i < 4 && !defined($event->{'LEADTIME'});$i++) {+
- if ($eventsbyuid{$uid}{$curreventday->subtract( days => $i+1 )->epoch() }) {+
- $event->{'LEADTIME'} = $i;+
- }+
- }+
} else { } else {
- $eventsbyuid{$uid} = $event;+ # if there's no due or start date, just make it now.
- my $curreventday = $event->{'DTSTART'}->clone;+ $todo->{'SORT'} = $now;
- $curreventday->truncate( to => 'day' );+
- $eventsbyuid{$uid}{$curreventday->epoch()} =1;+
} }
- + push(@newtodos,$todo);
- }+ }
- }+ if (! (scalar @newtodos)) {
- }+ $output .= 'MSG No Todos%"%"%'."\n";
-}+ return $output;
-for $yearkey (sort keys %{$events} ) {+ }
- my $yearevents = $events->{$yearkey};+# Now sort on the new Due dates and print them out.
- for $monkey (sort {$a <=> $b} keys %{$yearevents}){+ for $todo (sort { DateTime->compare($a->{'SORT'}, $b->{'SORT'}) } @newtodos) {
- my $monevents = $yearevents->{$monkey};+ my $due = $todo->{'SORT'}->clone();
- for $daykey (sort {$a <=> $b} keys %{$monevents} ) {+ if (defined($todo->{'DTSTART'}) && defined($todo->{'DUE'})) {
- my $dayevents = $monevents->{$daykey};+ # Lead time is duration of task + lead time
- for $uid (sort {+ my $diff = ($todo->{'DUE'}->delta_days($todo->{'DTSTART'})->days())+$DEFAULT_LEAD_TIME;
- DateTime->compare($dayevents->{$a}->{'DTSTART'}, $dayevents->{$b}->{'DTSTART'})+ $leadtime = "+".$diff;
- } keys %{$dayevents}) {+ } else {
- my $event = $dayevents->{$uid};+ $leadtime = "+".$DEFAULT_LEAD_TIME;
- if (exists($event->{'LEADTIME'})) {+ }
- $leadtime = "+".$event->{'LEADTIME'};+ $output .= "REM ".$due->month_abbr." ".$due->day." ".$due->year." $leadtime MSG \%a $todo->{'SUMMARY'}\%\"\%\"\%\n";
- } elsif ($event->{'CATEGORIES'} && $event->{'CATEGORIES'} eq 'Projects') {+ }
- $leadtime = "+".$PROJECT_LEAD_TIME;+ return $output;
- } 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 " MSG %a %2 ";+
- } else {+
- print " MSG %a ";+
- }+
- no warnings; # It's possible not to have a SUMMARY. +
- print "%\"$event->{'SUMMARY'}";+
- print " at $event->{'LOCATION'}" if $event->{'LOCATION'};+
- print "\%\"%\n";+
- }+
- }+
- }+
} }
-# expects 'todos' hashref from iCal::Parser is input 
-# prints Todos to stdout 
-# returns nothing 
-sub _process_todos { 
- my $todos = shift;  
- print 'MSG '.$label.' ToDos:%"%"%'."\n";+#######################################################################
 +#
 +# Main Program
 +#
 +######################################################################
-# For sorting, make sure everything's got something+print _process_todos($hash->{'todos'}) if $PROCESS_TODOS;
-# To sort on. +
- my $now = DateTime->now;+
- for $todo (@{$todos}) {+
- # remove completed items+
- if ($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);+
- }+
-# 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();+
- 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;+
- }+
- print "REM ".$due->month_abbr." ".$due->day." ".$due->year." $leadtime MSG \%a $todo->{'SUMMARY'}\%\"\%\"\%\n";+
- }+
- return undef;+
-}+
 +my ($leadtime, $yearkey, $monkey, $daykey,$uid,%eventsbyuid);
 +print 'REM PRIORITY 9999 MSG %"%"%'."\n";
 +print 'REM PRIORITY 9999 MSG Calendar 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 < 4 && !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'};
 + } elsif ($event->{'CATEGORIES'} && $event->{'CATEGORIES'} eq 'Projects') {
 + $leadtime = "+".$PROJECT_LEAD_TIME;
 + } 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; exit 0;
- +#:vim set ft=perl ts=4 sts=4 expandtab :
-# vim: ft=perl+
- +
</pre> </pre>
[[Category: Remind]][[Category: Software]] [[Category: Remind]][[Category: Software]]

Revision as of 16:32, 21 March 2007

Contents

Ical2Rem

A script to convert from the iCal / ICS format the format used by Remind. It converts the calendar entries as well as the To Do items. It uses the Due Date as the Remind date, or the current date if there's no Due date.

This version is modified from the one on the author's site to include the following changes: It expects input through a pipe, and has an optional "--label" option if you want to give the refer the calendar with a more descriptive label than "Calendar".

A Subversion repository will be available soon for "official" versions. Version 0.4, as of 2007-03-21, is available here and at the author's site.

Example uses:

cat /path/to/my/other_file.ics | ical2rem >~/.ical2rem

Or process a remote file, such as one from Google calendar:

wget -q -O- http://path.com/to/my/google.ics | ical2rem --label "Gcal" >~/.ical2rem

You may wish to make a cron entry to automate this:

0-59/15 * * * *  cat /path/to/my/other_file.ics | ical2rem >~/.ical2rem
In the ~/.reminders file, include the new file:
INCLUDE ./.ical2rem

Installation

Download the script from The Author's Site or copy the following script and save to a file named "ical2rem" which should be placed in a "bin" directory in your executable path. /usr/local/bin/ or ~/bin are good candidates. You may need to install some Perl modules first. Try cpan -i iCal::Parser DateTime as root. If that doesn't work, consult general documentation for installing Perl modules.

For installation on OS X, see iCal2Remind OSX HOWTO. It refers to the original version of ical2rem. It also includes some additional tips on installing the Perl modules.

License

The code for ical2rem.pl has now been released under the GPL.

Author

The code was originally created by Justin B. Alcorn, inspired by a message thread on the 43Folders board discussing conversion scripts written in Applescript. Please consider leaving a comment for the author

#!/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.4 2007-03-21
#       - 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

 --label                       Calendar name (Default: Calendar)
 --lead-time           Advance days to start reminders (Default: 3)
 --todos, --no-todos   Process Todos? (Default: Yes)

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 --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 --lead-time 3

How may days in advance to start getting reminders about the events. Defaults to 3.

=head2 --no-todos

  ical2rem --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.

=cut

use strict;
use iCal::Parser;
use DateTime;
use Getopt::Long 2.24 qw':config auto_help';
use vars '$VERSION';
$VERSION = 0.4;

# Declare how many days in advance to remind
my $DEFAULT_LEAD_TIME = 3;
my $PROJECT_LEAD_TIME = 14; # used with Category is 'Projects'
my $PROCESS_TODOS     = 1;

my $label = 'Calendar';
GetOptions (
        "label=s"     => \$label,
        "lead-time=i" => \$DEFAULT_LEAD_TIME,
        "todos!"          => \$PROCESS_TODOS,
);

my $month = ['None','Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'];
my $hash = iCal::Parser->new->parse(\*STDIN);

##############################################################
#
# 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 PRIORITY 9999 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)) {
                $output .= 'MSG No Todos%"%"%'."\n";
                return $output;
        }
# 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();
                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 MSG \%a $todo->{'SUMMARY'}\%\"\%\"\%\n";
        }
        return $output;
}


#######################################################################
#
#  Main Program
#
######################################################################

print _process_todos($hash->{'todos'}) if $PROCESS_TODOS;

my ($leadtime, $yearkey, $monkey, $daykey,$uid,%eventsbyuid);
print 'REM PRIORITY 9999 MSG %"%"%'."\n";
print 'REM PRIORITY 9999 MSG Calendar 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 < 4 && !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'};
                } elsif ($event->{'CATEGORIES'} && $event->{'CATEGORIES'} eq 'Projects') {
                    $leadtime = "+".$PROJECT_LEAD_TIME;
                } 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 :
Personal tools