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

Projects, not contexts, silly

In my last post, I outlined my projected context structure in OmniFocus. When I was just about done creating this context structure, and placing a few sample tasks that I already had, I realized that contexts such as ‘Executing Tech Talks with the team’ is not really a context. It’s a Project. So I rewrote all of my mind map contexts as projects instead, reserving contexts for very simple things:

B5CD085C-2781-439D-9A29-71C9B5C19179

My one goal here is to:
a.) Be able to “have a well-rounded day”, i.e. approx. 1 task included in my Forecast from each of my (now) Project buckets. (this is possible now with all my new projects and sub projects)
b.) Be able to quickly classify a task when I am entering things on the fly. If it’s not dead easy to add a task, I am not going to use the system. (this is possible now with my very limited set of contexts)

So that was a big paradigm shift. I just assumed contexts needed to be very intricate, but in fact my Project usage of OF2 just needed to be taken to the next level.

Next up: Applescripting OF2 to create a Perspective of 1 task from each of my Projects. Stay tuned..

Mind Mapping My Life

There are a few problems that have been nagging me about my task lists:

  1. Not having the right energy at the right time
  2. Losing perspective over what I should be doing right now for maximum effect
  3. Focusing too much on some areas of my life at the exclusion of others
  4. Having task list contexts that are not effective

The first problem seems to be answered in a few books I am aware of, like The Power of Full Engagement, and Two Awesome Hours (which I just purchased and have yet to read).

The second and third ones I think are born of the last problem, which is in regards to my use of OmniFocus 2 for all of my task tracking. I have pretty obvious @contexts like @home (for chores), @kitchen (for cooking), @store, @work, but then also non-location based ones like @read (for paper or digital books), @macbook (for really anything, it’s too general), and @watch (for Netflix, Amazon Prime, Tivo, or technical videos online).

MY GOAL: I want to feel like a well rounded person. I want to go through let’s say 2 or 3 days and feel like all the tasks that I accomplished contributed to all the major areas of my life. I’m a computer programmer, and look forward to the weekend so I could learn some new technology, but frequently Monday rolls around again and all I’ve done is cook, clean the kitchen, vacuum the floors, laundry, cleaned the bathroom. I seem to think that all of that is preparatory work to being able to settle back with a clear mind and be able to pour myself 100% into a project. But that’s the subject of another post, or a therapy session.

Back to having proper @contexts; for a while now, I’ve been trying to find some sort of better guidance around creating @contexts, but not really gotten an Aha! from anything. What is a perfect context that means “having enough energy, at a computer, on a non-recent project, while having an hour of uninterrupted time”??

My @contexts are just too generic- take the @watch for example. Technical videos and The Bachelor just shouldn’t belong in the same @context. And @macbook is also too generic. Half my life is on my computer, but I can’t have one @context for half of my life, and there are very different purposes for different computer tasks, so @macbook can’t be an effective @context.

As an experiment in just laying it all out on the table, making a mind map of everything that I have responsibility for, I did just that. My hope was that that exercise would be translatable to OmniFocus contexts, which would then be much more balanced overall and better segmented according to the ACTUAL areas of my life. Here are portions of what I came up with (using MindNode Lite):

BP3

BP2

BP1

As should be apparent, @contexts based on these categories would be much less location based, and more life area based. And watching technical videos online (@Professional:PersonalDev:WatchingVideos) will now be in a distinct @context than The Bachelor (@Recreation:TV:Tivo).

Next up: creating the OmniFocus @contexts based on this and figuring out how to track ‘balancedness’. Should each day’s set of planned tasks be one task from each of the main subdivisions in the mind map? Or something else? I’m thinking that some of these ‘leaf nodes’ should be tasks and some of them should be child contexts to store other tasks.

Bye Bye Sandy

For the last year or so, I’ve been subscribed to a free service called I Want Sandy. Sandy is like a virtual calendar assistant, in that it allocates me a personal email address, which I can send a specially formatted email to. That email could be something like “remind me to pick up my dry cleaning next Wednesday at 6:45pm”, in the subject line or body of the email. At that time, I get an email back from Sandy with a reminder about my dry cleaning.  I certainly am not a power user, using it maybe once or twice a week, but enough to make it convenient for those occasional reminders.

Well, I just got a notice from them that they are shutting their virtual doors on Dec 19. Looks like Twitter has bought their technology, and their CEO is also joining Twitter.  Now, on to look for alternatives:

http://twitter.com/timer – Pros: backed by Twitter’s solid foundation Cons: Limited by Twitter’s access methods – text or browser, no email
http://gopingme.com/ – Pros: They seem to be ready for Sandy’s abandoned users, as they have a tutorial for ex-Sandy users on their home page. Just like Sandy, you can postpone event reminders (reply to reminder email with “remind me about this in 1 day”) type of thing.
http://www.rememberthemilk.com – Pros: Has email-task and receive-email-reminder functionality, but they seems to be 2 distinct operations. Cons: seems to be a full fledged task management system, which I already use Omnifocus for. I really need something just to replace Sandy with- a simple email/get-reminder-later system.

Looks like I will try http://gopingme.com, even with the cheesy service name.