Braving the Applescript syntax kerfuffle

Ok, I made it through. Unscathed, relatively. Applescript bowed to my power. Well, sort of.

The problem: My workflow in Omnifocus is to use the task defer date heavily to basically build up a task list for today. However, I have several “spheres” of tasks, like reading books, taking online classes, exercising.. But if I defer ‘book’ tasks for a few days, the next day contains like 8 reading tasks. Currently I keep ‘rebalancing’ these kind of pile up days by hand. But that should be scriptable.

My rules and Applescript to run my “Rebalance” script are up on github.

It turned out that I needed to form a few different ‘types’ of rules:

  • if any today task contains more than X number in a specific context, then push anything over X to the next day
  • if any today task contains more than X number in a specific PROJECT, then push anything over X to the next day
  • if any today task contains more than X number in a specific FOLDER, then push anything over X to the next day
  • if there is not a task in X project/folder today, then add a defer date to one task in X project/folder for the coming X days

These rules are realized in these examples:

  • there should be at most 1 video task per day (Project: Videos)
  • there should be at most 1 class per day (Folder: Classes / Skills Project: Tech)
  • there should be 1 interview practice thing every 4 days (if there isn’t any Interview Practice stuff today, set one for 4 days from now) (Project: Interview Practice)

And that’s it! The hardest part was definitely the Applescript syntax, but there are so many examples on the web, that you can cobble anything together. The next hardest thing was figuring out my personal rules. That just takes time and living with your own schedule and replicating what you do manually every day, into replicatable rules. What do you always find yourself always doing to your Forecast?

Full script:

property todayStr : "Today"
property missingContextStr : "" -- string when context is missing
property barStr : "--" -- BAR string after header
property preStr : "?" & tab -- PRESTRING prior to each list item
property actGroupStr : "Project Group" -- pre-string when task is action group (shows when expandGroup = false)
property sepStr : " : " -- SEPARATOR string between context and task

tell application "OmniFocus"
    tell front document
        log "STARTED Rebalancing"
        
        -- get list of today's tasks
        set nowDate to current date
        set nowHours to hours of nowDate
        
        set todayDate to current date
        set todayDate's hours to 0
        set todayDate's minutes to 0
        set todayDate's seconds to 0
        set tomorrowDate to todayDate + 1 * days
        set todaysTasks to (flattened tasks where completed is false and ((defer date ? todayDate and defer date < tomorrowDate) or (due date ? todayDate and due date < tomorrowDate)))
        
        set maxNumReadingTasks to 2
        set currentNumReadingTasks to 0
        set maxNumVideoTasks to 1
        set currentNumVideoTasks to 0
        set maxNumClassTasks to 1
        set currentNumClassTasks to 0
        set maxNumExerciseTasks to 3
        set currentNumExerciseTasks to 0
        set isInterviewPracticeToday to false
        set isProgrammingPracticeToday to false
        set isRelationshipTaskToday to false
        set isRecreationalVideoToday to false
        
        repeat with t in todaysTasks
            
            if (nowHours > 10) then
                if (name of t = "work out") then
                    set t's completed to true
                end if
                if (name of t = "run") then
                    log my logThis(t, tomorrowDate)
                    set t's defer date to tomorrowDate
                end if
                if (name of t = "do shoulder PT exercises") then
                    log my logThis(t, tomorrowDate)
                    set t's defer date to tomorrowDate
                end if
                if (name of t = "make green smoothie (add Omega 3)") then
                    set t's completed to true
                end if
                if (name of t = "write 3 pages") then
                    log my logThis(t, tomorrowDate)
                    set t's defer date to tomorrowDate
                end if
            end if
            
            if (context of t is not missing value) then
                if (name of context of t = "book") then
                    set currentNumReadingTasks to currentNumReadingTasks + 1
                    if (currentNumReadingTasks > maxNumReadingTasks) then
                        log my logThis(t, tomorrowDate)
                        set t's defer date to tomorrowDate
                        
                    end if
                end if
            end if
            
            if (containing project of t is not missing value) then
                if (name of containing project of t = "Videos") then
                    set currentNumVideoTasks to currentNumReadingTasks + 1
                    if (currentNumVideoTasks > maxNumVideoTasks) then
                        log my logThis(t, tomorrowDate)
                        set t's defer date to tomorrowDate
                        
                    end if
                end if
                
                if (name of containing project of t = "Maintenance") then
                    set currentNumExerciseTasks to currentNumExerciseTasks + 1
                    if (currentNumExerciseTasks > maxNumExerciseTasks) then
                        log my logThis(t, tomorrowDate)
                        set t's defer date to tomorrowDate
                        
                    end if
                end if
                
                if (name of containing project of t = "Interview Practice") then
                    set isInterviewPracticeToday to true
                end if
                
                if (name of containing project of t = "Practice") then
                    set isProgrammingPracticeToday to true
                end if
                
            end if
            
            if (folder of containing project of t is not missing value) then
                if (name of folder of containing project of t = "Classes / Skills" and name of containing project of t = "Tech") then
                    set currentNumClassTasks to currentNumClassTasks + 1
                    if (currentNumClassTasks > maxNumClassTasks) then
                        log my logThis(t, tomorrowDate)
                        set t's defer date to tomorrowDate
                        
                    end if
                end if
                
                if (name of folder of containing project of t = "Relationship") then
                    set isRelationshipTaskToday to true
                end if
                
                if (name of folder of containing project of t = "Recreation / Video" and name of containing project of t = "Tech") then
                    set isRecreationalVideoToday to true
                end if
                
            end if
            
        end repeat
        
        if (isInterviewPracticeToday = false) then
            my deferInProject("Interview Practice", 4, todayDate)
        end if
        
        if (isProgrammingPracticeToday = false) then
            my deferInProject("Practice", 3, todayDate)
        end if
        
        if (isRelationshipTaskToday = false) then
            my deferInFolder("Relationship", 7, todayDate)
        end if
        
        if (isRecreationalVideoToday = false) then
            my deferInFolder("Video", 4, todayDate)
        end if
        
        log "COMPLETED Rebalancing"
        
    end tell
    
end tell

on dateISOFormat(theDate)
    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))
    return y & "-" & m & "-" & d
end dateISOFormat

on getTextTask(ListItem)
    using terms from application "OmniFocus"
        set theContext to missingContextStr
        if (context of ListItem is not missing value) then
            set theContext to preStr & (name of context of ListItem) & sepStr
        end if
        set theAction to (name of ListItem)
        set theProject to ""
        if (containing project of ListItem is not missing value) then
            set theProject to (name of containing project of ListItem) & sepStr
        end if
        set deferDate to ""
        if (defer date of ListItem is not missing value) then
            set deferDate to sepStr & my dateISOFormat(defer date of ListItem)
        end if
        
        -- return theContext & theProject & theAction & deferDate --
        return theAction & deferDate --
    end using terms from
end getTextTask

on deferInProject(projectName, deferDays, todayDate)
    tell application "OmniFocus"
        tell front document
            set customProject to first flattened project where its name = projectName
            set customTasks to (flattened tasks of customProject where completed is false)
            repeat with t in customTasks
                log my logThis(t, todayDate + deferDays * days)
                set t's defer date to todayDate + deferDays * days
                return
            end repeat
        end tell
    end tell
end deferInProject

on deferInFolder(folderName, deferDays, todayDate)
    tell application "OmniFocus"
        tell front document
            set customFolder to first flattened folder where its name = folderName
            set customProjects to (flattened projects of customFolder)
            repeat with p in customProjects
                set customTasks to (flattened tasks of p where completed is false)
                repeat with t in customTasks
                    log my logThis(t, todayDate + deferDays * days)
                    set t's defer date to todayDate + deferDays * days
                    return
                end repeat
            end repeat
        end tell
    end tell
end deferInFolder

on logThis(ListItem, deferDate)
    set toLog to "Deferring " & my getTextTask(ListItem) & " to " & my dateISOFormat(deferDate) & return
    return toLog
end logThis

Leave a Reply

Your email address will not be published. Required fields are marked *