Last active
April 11, 2022 08:33
-
-
Save leonroy/5b764856d56990e1dbac to your computer and use it in GitHub Desktop.
Convert Things database to CSV. This script produces two CSVs on the user's Desktop. One for todos and the other for projects. It requires that Things.app is installed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
--------- | |
-- Convert Things (from Cultured Code) database to CSV | |
-- https://culturedcode.com/things/ | |
-- | |
-- Version 1.0 | |
-- | |
-- This script produces two CSVs on the user's Desktop. One for todos and the other for projects. | |
-- It requires that Things.app is installed. | |
-- | |
-- Things todos and projects can be tied either using the project name in the todos CSV or better | |
-- the project id in the todos CSV since this is guaranteed to be unique. | |
-- | |
-- Project Areas are not present since I had difficulty pulling them out and don't use them so | |
-- didn't investigate further. | |
-- | |
-- Known Issues: | |
-- This script is slow as molasses. Seems to cause Things to hit 100% CPU usage on a single core. | |
-- Guessing perhaps the Things Applescript implementation isn't parallelized at all or my | |
-- first attempt at Applescript-ing needs some refinement! | |
-- | |
-- Also I haven't delineated Inbox, Today, Next, Scheduled, Someday etc. A grievous oversight I know | |
-- but to be frank I only use Today and I couldn't come up with an elegant approach to pulling those | |
-- categories out of Things without hard coding them into my script and iterating over each one in | |
-- turn. | |
--------- | |
# Setup todo file | |
set todoFilePath to (path to desktop as Unicode text) & "Things Backup - todo - " & replace_chars(dateISOformat(current date), ":", "-") & ".csv" | |
set todoFile to (open for access file todoFilePath with write permission) | |
set eof of todoFile to 0 | |
write "name,status,tag names,cancellation date,due date,modification date,contact,project name, project id,area,activation date,id,completion date,creation date,notes" & linefeed to todoFile | |
# Setup project file | |
set projectFilePath to (path to desktop as Unicode text) & "Things Backup - project - " & replace_chars(dateISOformat(current date), ":", "-") & ".csv" | |
set projectFile to (open for access file projectFilePath with write permission) | |
set eof of projectFile to 0 | |
write "name,status,tag names,cancellation date,due date,modification date,contact,area,activation date,id,completion date,creation date,notes" & linefeed to projectFile | |
tell application "Things" | |
-- debug - can use the below counter to exit the todo loop after N iterations for debugging | |
-- set counter to 0 | |
repeat with toDo in to dos | |
set toDoName to my escapeTextToCsv(my trim_line(my formatEmptyValue(name of toDo), " ", 2)) | |
set toDoStatus to my formatEmptyValue(status of toDo) | |
set toDoTags to my escapeTextToCsv(my formatEmptyValue(tag names of toDo)) | |
set toDoCancellation to my dateISOformat(cancellation date of toDo) | |
set toDoDue to my dateISOformat(due date of toDo) | |
set toDoModification to my dateISOformat(modification date of toDo) | |
set toDoContact to my formatEmptyValue(contact of toDo) | |
set toDoProject to project of toDo | |
set toDoProjectName to "" | |
set toDoProjectId to "" | |
if (toDoProject is not missing value) then | |
set toDoProjectName to name of toDoProject | |
set toDoProjectId to id of toDoProject | |
end if | |
set toDoAreaName to "" | |
set toDoActivation to my dateISOformat(activation date of toDo) | |
set toDoId to id of toDo | |
set toDoCompletion to my dateISOformat(completion date of toDo) | |
set toDoCreation to my dateISOformat(creation date of toDo) | |
set toDoNotes to my escapeTextToCsv(my trim_line(my formatEmptyValue(notes of toDo), " ", 2)) | |
write toDoName & "," & toDoStatus & "," & toDoTags & "," & toDoCancellation & "," & toDoDue & "," & toDoModification & "," & toDoContact & "," & toDoProjectName & "," & toDoProjectId & "," & toDoAreaName & "," & toDoActivation & "," & toDoId & "," & toDoCompletion & "," & toDoCreation & "," & toDoNotes & linefeed to todoFile | |
-- debug - can use the below counter to exit the todo loop after N iterations for debugging (default 10) | |
--get properties of toDo | |
--set counter to 1 + (counter) | |
--if (counter) is 10 then | |
-- exit repeat | |
--end if | |
end repeat | |
repeat with pr in projects | |
set prName to my escapeTextToCsv(my trim_line(my formatEmptyValue(name of pr), " ", 2)) | |
set prStatus to my formatEmptyValue(status of pr) | |
set prTags to my escapeTextToCsv(my formatEmptyValue(tag names of pr)) | |
set prCancellation to my dateISOformat(cancellation date of pr) | |
set prDue to my dateISOformat(due date of pr) | |
set prModification to my dateISOformat(modification date of pr) | |
set prContact to my formatEmptyValue(contact of pr) | |
set prAreaName to "" | |
set prActivation to my dateISOformat(activation date of pr) | |
set prId to id of pr | |
set prCompletion to my dateISOformat(completion date of pr) | |
set prCreation to my dateISOformat(creation date of pr) | |
set prNotes to my escapeTextToCsv(my trim_line(my formatEmptyValue(notes of pr), " ", 2)) | |
write prName & "," & prStatus & "," & prTags & "," & prCancellation & "," & prDue & "," & prModification & "," & prContact & "," & prAreaName & "," & prActivation & "," & prId & "," & prCompletion & "," & prCreation & "," & prNotes & linefeed to projectFile | |
-- debug -- | |
#get properties of pr | |
end repeat | |
end tell | |
on formatEmptyValue(theValue) | |
if theValue is missing value then | |
set theValue to "" | |
end if | |
return theValue | |
end formatEmptyValue | |
on dateISOformat(theDate) | |
if theDate is missing value then | |
set theDate to "" | |
else | |
set y to text -4 thru -1 of ("0000" & (year of theDate)) | |
set m to text -2 thru -1 of ("00" & ((month of theDate) as integer)) | |
set d to text -2 thru -1 of ("00" & (day of theDate)) | |
set t to time string of theDate | |
set theDate to y & "-" & m & "-" & d & " " & t | |
end if | |
return theDate | |
end dateISOformat | |
on escapeTextToCsv(theText) | |
# replace all single quotes with double | |
# wrap all strings with double quotes | |
set the result to the replace_chars(theText, "\"", "\"\"") | |
set result to "\"" & result & "\"" | |
return result | |
end escapeTextToCsv | |
on replace_chars(this_text, search_string, replacement_string) | |
set AppleScript's text item delimiters to the search_string | |
set the item_list to every text item of this_text | |
set AppleScript's text item delimiters to the replacement_string | |
set this_text to the item_list as string | |
set AppleScript's text item delimiters to "" | |
return this_text | |
end replace_chars | |
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 |
Glad it proved useful after all these years 😁
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Super handy. Made my migration so much easier. Thank you!