Writing iCal events to a text file for Geektool purposes

Hey

I’ve been playing around with iCal and Geektool trying to come up with a way to show Upcoming Events and my To-Do’s on the Desktop. It is challenging. One Perl script I found looked promising but was entirely beyond me and I couldn’t get it to work. It had the advantage of working without opening iCal because it was parsing the calendar files themselves.

I’ve resorted to two Applescripts I found at lifehacker.com. Both of which write the events and to-dos to a text file that I then get Geektool to show on the desktop via the ‘less’ shell command. Not that I know what ‘less’ does, but it seems to work well.

I’m happy with the to-do list script - I’ve fiddled and managed to get it to include to-do’s without deadlines. It produces:

C u r r e n t T o - D o s
= = = = = = = = = = = = = =
Sort G e e k t o o l
L e a r n N A T S S t u f f
H a v e a s h a v e

The Events script is a little less perfect. I’ve already fiddled with it to get it to trim the seconds from the event start time and end time (which i thought were unnecessary.) I’ve also deleted the bit that tells you which calendar the event is from.

Currently it produces this:

E v e n t s f o r N e x t 7 D a y s
= = = = = = = = = = = = = = = = = = = = = =
S p e c s a v e r s
T h u r s d a y , S e p t e m b e r 1 3 , 2 0 0 7 , @ 0 9 : 4 0 t o 1 0 : 4 0

D i n n e r w i t h C h r i s
T h u r s d a y , S e p t e m b e r 1 3 , 2 0 0 7 , @ 1 7 : 0 0 t o 1 8 : 0 0

This is ok, but I want to make it better. I don 't need to see each event with the individual date underneath. I’d rather see:

E v e n t s f o r N e x t 7 D a y s
= = = = = = = = = = = = = = = = = = = = = =
T h u r s d a y , S e p t e m b e r 1 3 , 2 0 0 7
S p e c s a v e r s @ 0 9 : 4 0 t o 1 0 : 4 0
D i n n e r w i t h C h r i s @ 1 7 : 0 0 t o 1 8 : 0 0

See what I mean? Rather like the brilliant icalevents widget here http://www.benkazez.com/icalevents.php

So that’s my main question. I want to make it as neat and sensible as possible basically. Can anyone make any progress on this?

Also does anyone have any better ideas? Currently I have made iCal dockless and it runs in the background invisibly taking up a tiny bit of CPU which is not ideal, but I have the scripts run every hour via Cronnix so iCal stays open inevitably. Naturally this impacts on geektool’s up-to-the-minute accuracy. If anyone could come up with a totally different way to allow iCal to remain closed (parsing the calendar files?) it would be amazing. I’m fairly rubbish at scripting so any help would be massively appreciated.

The script is here, with the original author’s annotations and details etc:

-- this script runs automatically at about 7.00 every morning, and can be run manually during the day for cleanup if necessary
-- I'm going to write to a unix text file, because I use geektool and cat
set line_feed to (ASCII character 10)
set this_date to current date
set the start_day to (this_date - (time of this_date))
-- go out 7 days to the last second before midnight
set the end_day to (the start_day + (7 * days) - 1)
set all_events to {}
set what_cal to {}
-- I only use some of my calendars for this
set ok_cals to {"Home"}
-- this is the text file (the directory syncs to my iPod as well)
set events_file to "Macintosh HD:Users:Nathan 2:Documents:ical-geektool:iCalevents.txt"
try
	tell application "iCal"
		repeat with i from 1 to count of ok_cals
			set this_calendar to (the first calendar whose title is item i of ok_cals)
			set returned_events to {}
			set the returned_events to (every event of this_calendar whose start date is greater than or equal to start_day and start date is less than or equal to the end_day and end date is greater than or equal to (current date))
			repeat with i from 1 to the count of returned_events
				set this_item to item i of returned_events
				set all_events to all_events & {this_item}
				-- I use what_cal to preserve the calendar each event is in
				set what_cal to what_cal & {name of this_calendar}
			end repeat
		end repeat
		-- Craig Smith's bubblesort follows; I'm lazy, so I just put it inline
		-- the goal here is to to sort all of the events by date
		repeat with i from length of all_events to 2 by -1 --> go backwards
			repeat with j from 1 to i - 1 --> go forwards
				-- there might be an easier way to set the sort items
				set j_props to the properties of item j of all_events
				set j_date to start date of j_props
				set j1_props to the properties of item (j + 1) of all_events
				set j1_date to the start date of j1_props
				if j_date > j1_date then
					set {all_events's item j, all_events's item (j + 1)} to {all_events's item (j + 1), all_events's item j} -- swap
					set {what_cal's item j, what_cal's item (j + 1)} to {what_cal's item (j + 1), what_cal's item j} -- swap in lockstep
				end if
			end repeat
		end repeat
	end tell
	
	if all_events is equal to {} then
		set the master_data to ""
	else
		set the master_data to "Events for Next 7 Days" & line_feed & "======================" & line_feed & events_2_text(all_events, what_cal)
	end if
	
	write_to_file(master_data, events_file)
	
	-- I found this useful for troubleshooting before the write-to-file stage
	(*tell application "TextEdit"
		activate
		open events_file
	end tell*)
	
on error error_message number error_number
	if the error_number is not -128 then
		display dialog error_message buttons {"OK"} default button 1
	end if
end try

on trim_line(this_text, trim_chars, trim_indicator)
	-- 0 = beginning, 1 = end, 2 = both
	set x to the length of the trim_chars
	-- TRIM BEGINNING
	if the trim_indicator is in {0, 2} then
		repeat while this_text begins with the trim_chars
			try
				set this_text to characters (x + 1) thru -1 of this_text as string
			on error
				-- the text contains nothing but the trim characters
				return ""
			end try
		end repeat
	end if
	-- TRIM ENDING
	if the trim_indicator is in {1, 2} then
		repeat while this_text ends with the trim_chars
			try
				set this_text to characters 1 thru -(x + 1) of this_text as string
			on error
				-- the text contains nothing but the trim characters
				return ""
			end try
		end repeat
	end if
	return this_text
end trim_line

--- stripped down version of apple's write subroutine
on write_to_file(this_data, target_file)
	tell application "Finder"
		try
			set the target_file to the target_file as text
			set the open_target_file to open for access file target_file with write permission
			set eof of the open_target_file to 0
			write this_data to the open_target_file starting at eof
			close access the open_target_file
			return true
		on error
			try
				close access file target_file
			end try
			return false
		end try
	end tell
end write_to_file

on events_2_text(the_events, the_cal)
	set line_feed to (ASCII character 10)
	
	tell application "iCal"
		set the event_count to the count of the_events
		set event_summary to ""
		if the event_count is 0 then
			return the event_summary
		end if
		repeat with i from 1 to the event_count
			set this_event to item i of the_events
			set the event_properties to the properties of this_event
			set summary_text to the summary of the event_properties
			--set this_location to the location of the event_properties
			--if this_location is missing value then set this_location to "N/A"
			--set the location_text to "Location:" & tab & this_location
			set all_day to allday event of the event_properties
			set take_off to start date of the event_properties
			set wheels_down to end date of the event_properties
			set start_date to date string of (get start date of the event_properties)
			set end_date to date string of (get end date of the event_properties)
			set untrimmedstarting_time to time string of (get start date of the event_properties)
			set starting_time to my trim_line(untrimmedstarting_time, ":0", 1)
			set untrimmedending_time to time string of (get end date of the event_properties)
			set ending_time to my trim_line(untrimmedending_time, ":0", 1)
			set event_description to the description of the event_properties
			-- I use the @@ to test whether to convert an event to a to-do using a different script, so it's not "notes"
			if (event_description is missing value or event_description is equal to "@@") then set event_description to ""
			set the date_text to start_date
			-- the if statement below eliminates times for all-day events and does a couple other cleanup tasks 
			if not all_day then
				if start_date is not equal to end_date then
					set the date_text to date_text & ", " & starting_time & " to " & end_date & ", " & ending_time
				else
					set the date_text to date_text & ", @ " & starting_time
					if (take_off + (10 * minutes)) is less than wheels_down then
						set the date_text to date_text & " to " & ending_time
					end if
				end if
			else
				if wheels_down is greater than (take_off + (1 * days) + 1) then
					set wheels_down to wheels_down - 1
					set end_date to date string of wheels_down
					set the date_text to date_text & " to " & end_date
				end if
			end if
			set the event_text to summary_text & line_feed & the date_text & line_feed
			if event_description is not "" then set event_text to event_text & "Notes:" & "  " & event_description & line_feed
			set the event_summary to the event_summary & the event_text & line_feed
		end repeat
		return the event_summary
	end tell
end events_2_text