I have been using Nirvana for my task list. I’d like to try OmniFocus. To get there, I had to convert my data.
First step is to export Nirvana data to CSV from their web site. The export isn’t perfect. They don’t identify which reference-items belong to which reference lists, for example.
Then import, using the following AppleScript. IT isn’t perfect, but it is a good start.
set theFile to (choose file with prompt "Select input file:" of type {"csv"})
global myOnHold, myActive, myDone, singletonProject, whenCowsComeHome
set taskLimit to 99000
set whenCowsComeHome to date "Thursday, December 31, 2099 at 1:00:00 PM"
set RefListName to "Reference Lists"
set RefListManualName to "Reference Item to Manually Move to Reference List"
tell application "OmniFocus"
set myOnHold to on hold
set myActive to active
set myDone to done
end tell
set progress total steps to 1
set progress additional description to "Starting up"
-- set progress completed steps to 1
-- First item is just column names, and it may contain a UTF BOM
set firstTime to true
set debugging to false
set myCounter to 1
set refList to mkFolder(RefListName)
set refListBad to mkProject(RefListManualName, myOnHold, "")
set singletonProject to mkProject("Miscellaneous", myActive, "")
tell application "OmniFocus" to tell default document to set singleton action holder of singletonProject to true
tell application "OmniFocus" to tell default document to set default singleton action holder of singletonProject to true
-- I use att and kpk as "Areas of Responsibility"
set kpkList to mkFolder("kpk")
set attList to mkFolder("att")
set csvText to csvToList(read theFile, {})
set progress completed steps to 1
set progress total steps to count of csvText
set progress additional description to "Projects"
with timeout of 300 seconds
---------------------------------------------------------------------
-- first time through, create projects
---------------------------------------------------------------------
repeat with anItem in csvText
if not firstTime then
set {aType, aParent, aState, aCompleted, aFocus, aName, aTags, aTime, aEnergy, aWaitingFor, aStartDate, aDueDate, aNotes} to text items of anItem
if aType is "Project" and aState is not "Trash" then
if aState = "" then
-- these are reference lists (Standalone), or items within reference lists (Standalone*).
if aParent = "Standalone" then
set newProj to mkProject(aName, myOnHold, aNotes)
-- handleFocus(aFocus, newProj) -- I do not focus my reference lists
-- handleCompleted(aCompleted, newProj) -- reference lists never get marked complete!
moveItemToFolderName(newProj, RefListName)
end if
else
set newProj to mkProject(aName, nirvanaStatusToOmniFocus(aState), aNotes)
handleFocus(aFocus, newProj)
handleCompleted(aCompleted, newProj)
-- I use att and kpk as "Areas of Responsibility"
set splitTags to split(aTags, ",")
if splitTags contains "att" then
moveItemToFolderName(newProj, "att")
else if splitTags contains "kpk" then
moveItemToFolderName(newProj, "kpk")
end if
end if
end if
end if
set firstTime to false
set progress completed steps to myCounter
set progress additional description to "Projects, record " & myCounter as text
set myCounter to myCounter + 1
end repeat
-- display dialog "Completed projects. Continue to Tasks?" giving up after 30000
set firstTime to true
set myCounter to 1
set progress total steps to count of csvText
set progress additional description to "Tasks"
---------------------------------------------------------------------
-- next time through, create tasks
---------------------------------------------------------------------
repeat with anItem in csvText
if not firstTime then
set {aType, aParent, aState, aCompleted, aFocus, aName, aTags, aTime, aEnergy, aWaitingFor, aStartDate, aDueDate, aNotes} to text items of anItem
if aState is not "Trash" then
if aType is "Project" and aState is "" and aParent is "Standalone*" then
-- these are reference lists (Standalone), or items within reference lists (Standalone*).
-- display dialog "adding reference item:" & aName giving up after 30000
tell application "OmniFocus" to tell refListBad to set newTask to make new task with properties {name:aName, defer date:whenCowsComeHome}
-- handleCompleted(aCompleted, newTask) -- do not "complete" reference list items.
-- handleFocus(aFocus, newTask) -- I do not focus my reference lists.
moveTaskToProjectBasedOnTag(newTask, aTags)
else if aType is equal to "Task" then
set newTask to mkTask(aName, aParent, aState, aTags)
handleFocus(aFocus, newTask)
handleStartDate(aStartDate, newTask)
handleDueDate(aDueDate, newTask)
handleTaskNote(aNotes, newTask)
if aState is "Someday" and aStartDate is "" then
tell application "OmniFocus" to tell default document to set defer date of newTask to (current date) + weeks * 56 + (days * (random number from 1 to 30))
end if
if aState is "Inactive/Later" and aStartDate is "" then
tell application "OmniFocus" to tell default document to set defer date of newTask to (current date) + (days * 14) + (days * (random number from 1 to 7))
end if
handleCompleted(aCompleted, newTask)
end if
end if
end if
set firstTime to false
set progress completed steps to myCounter
set progress additional description to "Tasks, record " & myCounter as text
set myCounter to myCounter + 1
if myCounter > taskLimit then
display dialog "Quit early at task limit" giving up after 30000
error number -128
end if
end repeat
display dialog "Caution. \nThe repeat pattern on repeating tasks is not exported by Nirvana, so I can't import that. These items have had ' #REPEATING' appended to the subject.\r\n\n* Nirvana does not export sequetian/parallel. All projects have been marked sequential (because I'm feeling overwhelmed).\n* Tags have been dropped, because OmniFocus does not do tags.\n* Someday tasks are brought in with a start date of now + 13 months plus a random number of days (so you don't have to deal with them all at once)\n* 'Later' tasks come in as startDate of now + 2 weeks plus a small random number of days." giving up after 30000
end timeout
(*
From Nigel Garvey at http://macscripter.net/viewtopic.php?id=32322 ...
Assumes that the CSV text adheres to the convention:
Records are delimited by LFs or CRLFs (but CRs are also allowed here).
The last record in the text may or may not be followed by an LF or CRLF (or CR).
Fields in the same record are separated by commas (unless specified differently by parameter).
The last field in a record must not be followed by a comma.
Trailing or leading spaces in unquoted fields are not ignored (unless so specified by parameter).
Fields containing quoted text are quoted in their entirety, any space outside them being ignored.
Fields enclosed in double-quotes are to be taken verbatim, except for any included double-quote pairs, which are to be translated as double-quote characters.
No other variations are currently supported. *)
on csvToList(csvText, implementation)
-- The 'implementation' parameter must be a record. Leave it empty ({}) for the default assumptions: ie. comma separator, leading and trailing spaces in unquoted fields not to be trimmed. Otherwise it can have a 'separator' property with a text value (eg. {separator:tab}) and/or a 'trimming' property with a boolean value ({trimming:true}).
set {separator:separator, trimming:trimming} to (implementation & {separator:",", trimming:false})
script o -- Lists for fast access.
property qdti : getTextItems(csvText, "\"")
property currentRecord : {}
property possibleFields : missing value
property recordList : {}
end script
-- o's qdti is a list of the CSV's text items, as delimited by double-quotes.
-- Assuming the convention mentioned above, the number of items is always odd.
-- Even-numbered items (if any) are quoted field values and don't need parsing.
-- Odd-numbered items are everything else. Empty strings in odd-numbered slots
-- (except at the beginning and end) indicate escaped quotes in quoted fields.
set astid to AppleScript's text item delimiters
set qdtiCount to (count o's qdti)
set quoteInProgress to false
considering case
repeat with i from 1 to qdtiCount by 2 -- Parse odd-numbered items only.
set thisBit to item i of o's qdti
if ((count thisBit) > 0) or (i is qdtiCount) then
-- This is either a non-empty string or the last item in the list, so it doesn't
-- represent a quoted quote. Check if we've just been dealing with any.
if (quoteInProgress) then
-- All the parts of a quoted field containing quoted quotes have now been
-- passed over. Coerce them together using a quote delimiter.
set AppleScript's text item delimiters to "\""
set thisField to (items a thru (i - 1) of o's qdti) as string
-- Replace the reconstituted quoted quotes with literal quotes.
set AppleScript's text item delimiters to "\"\""
set thisField to thisField's text items
set AppleScript's text item delimiters to "\""
-- Store the field in the "current record" list and cancel the "quote in progress" flag.
set end of o's currentRecord to thisField as string
set quoteInProgress to false
else if (i > 1) then
-- The preceding, even-numbered item is a complete quoted field. Store it.
set end of o's currentRecord to item (i - 1) of o's qdti
end if
-- Now parse this item's field-separator-delimited text items, which are either non-quoted fields or stumps from the removal of quoted fields. Any that contain line breaks must be further split to end one record and start another. These could include multiple single-field records without field separators.
set o's possibleFields to getTextItems(thisBit, separator)
set possibleFieldCount to (count o's possibleFields)
repeat with j from 1 to possibleFieldCount
set thisField to item j of o's possibleFields
if ((count thisField each paragraph) > 1) then
-- This "field" contains one or more line endings. Split it at those points.
set theseFields to thisField's paragraphs
-- With each of these end-of-record fields except the last, complete the field list for the current record and initialise another. Omit the first "field" if it's just the stub from a preceding quoted field.
repeat with k from 1 to (count theseFields) - 1
set thisField to item k of theseFields
if ((k > 1) or (j > 1) or (i is 1) or ((count trim(thisField, true)) > 0)) then set end of o's currentRecord to trim(thisField, trimming)
set end of o's recordList to o's currentRecord
set o's currentRecord to {}
end repeat
-- With the last end-of-record "field", just complete the current field list if the field's not the stub from a following quoted field.
set thisField to end of theseFields
if ((j < possibleFieldCount) or ((count thisField) > 0)) then set end of o's currentRecord to trim(thisField, trimming)
else
-- This is a "field" not containing a line break. Insert it into the current field list if it's not just a stub from a preceding or following quoted field.
if (((j > 1) and ((j < possibleFieldCount) or (i is qdtiCount))) or ((j is 1) and (i is 1)) or ((count trim(thisField, true)) > 0)) then set end of o's currentRecord to trim(thisField, trimming)
end if
end repeat
-- Otherwise, this item IS an empty text representing a quoted quote.
else if (quoteInProgress) then
-- It's another quote in a field already identified as having one. Do nothing for now.
else if (i > 1) then
-- It's the first quoted quote in a quoted field. Note the index of the
-- preceding even-numbered item (the first part of the field) and flag "quote in
-- progress" so that the repeat idles past the remaining part(s) of the field.
set a to i - 1
set quoteInProgress to true
end if
end repeat
end considering
-- At the end of the repeat, store any remaining "current record".
if (o's currentRecord is not {}) then set end of o's recordList to o's currentRecord
set AppleScript's text item delimiters to astid
return o's recordList
end csvToList
-- Get the possibly more than 4000 text items from a text.
on getTextItems(txt, delim)
set astid to AppleScript's text item delimiters
set AppleScript's text item delimiters to delim
set tiCount to (count txt's text items)
set textItems to {}
repeat with i from 1 to tiCount by 4000
set j to i + 3999
if (j > tiCount) then set j to tiCount
set textItems to textItems & text items i thru j of txt
end repeat
set AppleScript's text item delimiters to astid
return textItems
end getTextItems
-- From Nigel Garvey at http://macscripter.net/viewtopic.php?id=32322 ...
-- Trim any leading or trailing spaces from a string.
on trim(txt, trimming)
if (trimming) then
repeat with i from 1 to (count txt) - 1
if (txt begins with space) then
set txt to text 2 thru -1 of txt
else
exit repeat
end if
end repeat
repeat with i from 1 to (count txt) - 1
if (txt ends with space) then
set txt to text 1 thru -2 of txt
else
exit repeat
end if
end repeat
if (txt is space) then set txt to ""
end if
return txt
end trim
on mkFolder(fname)
with timeout of 300 seconds
tell application "OmniFocus"
tell front document
if not (folder fname exists) then
make new folder with properties {name:fname}
end if
return first folder whose name is fname
end tell
end tell
end timeout
end mkFolder
on mkProject(pname, pstatus, pnotes)
with timeout of 300 seconds
tell application "OmniFocus"
tell front document
if (flattened project pname exists) then
return first flattened project whose name is pname
end if
set tmpProject to make new project with properties {name:pname, status:pstatus, note:pnotes}
-- if pnotes is not "" then
-- set note of tmpProject to pnotes
-- end if
set sequential of tmpProject to true
return tmpProject
end tell
end tell
end timeout
end mkProject
on nirvanaStatusToOmniFocus(aState)
if aState is "Active" then
return myActive
else if aState is "Logbook" then
return myDone
else if aState is "Someday" then
return myOnHold
else
display dialog "Unable to convert state:" & aState giving up after 30000
error number -128
end if
end nirvanaStatusToOmniFocus
on moveItemToFolderName(aProj, aFolder)
with timeout of 300 seconds
tell application "OmniFocus"
tell front document
move aProj to (end of sections of (first folder whose name is aFolder))
end tell
end tell
end timeout
end moveItemToFolderName
on split(theString, theDelimiter)
-- save delimiters to restore old settings
set oldDelimiters to AppleScript's text item delimiters
-- set delimiters to delimiter to be used
set AppleScript's text item delimiters to theDelimiter
-- create the array
set theArray to every text item of theString
-- restore the old setting
set AppleScript's text item delimiters to oldDelimiters
-- return the result
return theArray
end split
on moveTaskToProject(aTask, aProjectName)
with timeout of 300 seconds
-- set progress additional description to aProjectName
if isProjectName(aProjectName) then
tell application "OmniFocus"
tell default document
set theProject to (first flattened project whose name is aProjectName)
move aTask to end of tasks of theProject
end tell
end tell
end if
end timeout
end moveTaskToProject
on moveTaskToProjectBasedOnTag(aTask, aTags)
with timeout of 300 seconds
set myList to my split(aTags, ",")
repeat with theItem in myList
if isProjectName(theItem) then
tell application "OmniFocus"
tell default document
set theProject to (first flattened project whose name is theItem)
move aTask to end of tasks of theProject
end tell
end tell
return
end if
end repeat
end timeout
display dialog "Nirvana does not identify the parent of a reference item in the export. If you will tag the ITEMS (not the containing list) with the name of the reference list (exactly), that will also tag the items and I can use that to put it in the right list." giving up after 30000
error number -128
end moveTaskToProjectBasedOnTag
on isProjectName(aName)
with timeout of 300 seconds
-- handle the special case, testing for a literal, because it is faster than talking to OmniFocus
if aName is "Standalone" then
return false
end if
tell application "OmniFocus"
tell default document
if flattened project aName exists then
return true
else
return false
end if
end tell
end tell
end timeout
end isProjectName
-- It is an active task if Parent is Standalone or project; state is Next or Inbox
on mkTask(aName, inParent, aState, aTags)
with timeout of 300 seconds
set tmpName to aName
if aState is "Scheduled/Repeating" then
set tmpName to aName & " #REPEATING"
end if
set aParent to inParent
if aParent is "Standalone" or aParent is "Standalone*" then
set aParent to "Miscellaneous"
end if
tell application "OmniFocus"
tell default document
if flattened project aParent exists then
set interestingProject to first flattened project whose name is aParent
if task tmpName of interestingProject exists then
set interestingTask to first task of interestingProject whose name is tmpName
delete interestingTask
end if
end if
set newTask to make new inbox task with properties {name:tmpName}
end tell
end tell
if aState is "Inbox" then
-- nothing. Leave it in Inbox, where it was created
else if aState is "Next" or aState is "Scheduled" or aState is "Scheduled/Repeating" or aState is "Someday" or aState is "Inactive/Later" or aState is "Logbook" then
moveTaskToProject(newTask, aParent)
else
display dialog "Unexpected state:" & aState & " for task:" & aName giving up after 30000
end if
if aState is "Logbook" then
tell application "OmniFocus"
tell default document
set completed of newTask to true
end tell
end tell
end if
end timeout
return newTask
end mkTask
-- convert yyyy-mm-dd to mm/dd/yyyy STRING
on parseNirvanaDate(nvDate)
set yyyy to text 1 thru 4 of nvDate
set mm to text 6 thru 7 of nvDate
set dd to text 9 thru 10 of nvDate
return date (mm & "/" & dd & "/" & yyyy & " 1:00:00 PM")
end parseNirvanaDate
on handleFocus(aFocus, aItem)
with timeout of 300 seconds
if aFocus is "Yes" then
tell application "OmniFocus" to tell default document to set flagged of aItem to true
end if
end timeout
end handleFocus
on handleCompleted(aCompleted, aItem)
-- if class of aItem as string is "project" then
-- display dialog ("aCompleted:" & aCompleted & ", class:" & class of aItem as string) & ", " & name of aItem
-- end if
with timeout of 300 seconds
if aCompleted is not "" then
tell application "OmniFocus"
tell default document
if (class of aItem as string) is "project" then
set status of aItem to done
else
set completed of aItem to true
end if
set completion date of aItem to my parseNirvanaDate(aCompleted)
end tell
end tell
end if
end timeout
end handleCompleted
on handleStartDate(aStartDate, aItem)
with timeout of 300 seconds
if aStartDate is not "" then
tell application "OmniFocus" to tell default document to set defer date of aItem to my parseNirvanaDate(aStartDate)
end if
end timeout
end handleStartDate
on handleDueDate(aDueDate, aItem)
with timeout of 300 seconds
if aDueDate is not "" then
set tmpDate to parseNirvanaDate(aDueDate)
tell application "OmniFocus"
tell default document
set due date of aItem to tmpDate
end tell
end tell
end if
end timeout
end handleDueDate
on handleTaskNote(aNotes, aTask)
with timeout of 300 seconds
tell application "OmniFocus" to tell default document to set note of aTask to aNotes
end timeout
end handleTaskNote
</code>