The Omni Group Forums

The Omni Group Forums (http://forums.omnigroup.com/index.php)
-   OmniFocus Extras (http://forums.omnigroup.com/forumdisplay.php?f=44)
-   -   Acting on all projects using Applescript (http://forums.omnigroup.com/showthread.php?t=15269)

fudster 2010-02-09 11:36 PM

Acting on all projects using Applescript
 
I've been having an awful time trying to obtain all the projects in my omnifocus document in Applescript.

I have a script working now, which I've mentioned in another thread. Basically it iterates over all projects and folders that are selected in the sidebar, and changes their "next review date" to the "last changed date", if the project has been changed since the last "Mark Review" event occured. I run this script once at the beginning of my daily "get clean" review, and it's working great for me. As a sidebar, I've submitted an enhancement request for Omni to make this functionality available out-of-the-box.

The problem is that I have to select all the folders in my sidebar in order to get the script to act on all of my projects. Not a great hardship obviously, but I use this script every day and I'd like to make it more convenient. I want the script to iterate over every single project, without having to select everything in the sidebar. But I haven't been able to figure out how to obtain the set of all projects.

I'm an experienced programmer, but I'll confess sometimes Applescript, and the structures under the hood of OF, really mystify me. Any hints anyone has, to help me get unstuck? Any snippets that'll spell it out?

I had hope that some of the tips and hints in this thread were going to help - didn't get me there though:
[URL]http://forums.omnigroup.com/showthread.php?t=3926&highlight=flattened[/URL]

Thanks!

Here's my script, which works now, abridged:

[CODE]on run
tell application "OmniFocus"
tell front document
tell document window 1

set selectedTrees to selected trees of sidebar

repeat with aTree in selectedTrees

if class of value of aTree is project then
my processProject(aTree)
else if class of value of aTree is folder then
my processFolder(aTree)
end if

end repeat

end tell
end tell
end tell
end run

on processFolder(theFolderAsTree)
using terms from application "OmniFocus"

repeat with aTree in trees of theFolderAsTree
if class of value of aTree is project then
my processProject(aTree)
else if class of value of aTree is folder then
my processFolder(aTree)
end if
end repeat

end using terms from
end processFolder

on processProject(theProjectAsTree)
using terms from application "OmniFocus"

set aProject to value of theProjectAsTree

set currentDateAtMidnight to date (short date string of (current date))

set oldReviewDate to last review date of aProject
set lastChangedDate to modification date of aProject
set nextReviewDateAtMidnight to next review date of aProject
if nextReviewDateAtMidnight > currentDateAtMidnight then

if oldReviewDate is not equal to lastChangedDate then
set next review date of aProject to lastChangedDate
end if
end if

end using terms from

end processProject
[/CODE]

curt.clifton 2010-02-10 03:31 PM

There is currently no direct way to get all the projects. The only way to access all the projects is to recurse over all the [I]sections[/I] of the front document. [URL="http://www.rose-hulman.edu/~clifton/software.html#VerifyNA"]My Verify Next Actions Exist[/URL] script does the necessary recursion. Feel free to mine it for ideas.

I really should post a script that just builds a list of all the projects. That wouldn't be too difficult. I'll try to throw something together, but no promises on the timing.

curt.clifton 2010-02-10 04:17 PM

I couldn't resist the productive procrastination. Here's a script template for processing every project in OF. I hope people will find it useful as a base for building their own scripts. There are three points of customization.
[LIST][*] A property lets you control whether projects inside dropped folders are included.
[*] One handler lets you decide whether or not a given project should be included in the list of projects to be processed based on the project's properties and elements.
[*] Another handler is called with a list of all the matching projects. You can customize this one to loop over the projects and update their properties.[/LIST]
[CODE]
(*
This script is a template for use in scripts that need to process every project in an OmniFocus database. The template provides stubs for two handlers that script developers can complete to create custom solutions. Script developers, see the handlers shouldProjectBeIncluded and handleProjects.

At this writing (Feb 10, 2010) there is no command in OmniFocus's dictionary to get all the projects, so this template uses a brute force recursion over the Library tree.

version 0.1, by Curt Clifton

Copyright © 2010, Curtis Clifton

Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

• Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.

• Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

version 0.1: Original release
*)

(* TODO: Change this property to control whether the script looks at projects inside folders that are hidden. The hidden property of a folder seems to correspond to the Dropped setting for a folder in the OmniFocus user interface. *)
property shouldLookInHiddenFolders : false

(*
Tests whether the given OmniFocus project should be included in the list of projects to be processed.

Developers should customize this handler if they wish to filter out some projects.
*)
on shouldProjectBeIncluded(theProject)
using terms from application "OmniFocus"
-- TODO: test theProject and return true to include it or false to exclude it
-- For example: return theProject is flagged
return true
-- -----------------------------------------------------------------------------
end using terms from
end shouldProjectBeIncluded

(*
Processes the list of all matched projects.

Developers should customize this handler to process the projects.
*)
on handleProjects(theMatchedProjects)
using terms from application "OmniFocus"
-- TODO: replace the following with code to process the projects (perhaps looping over them to process them individually)
return count of theMatchedProjects
-- -----------------------------------------------------------------------------
end using terms from
end handleProjects

(*
Main entry point.
*)
tell application "OmniFocus"
tell front document
set theSections to every section
set matchedProjects to my accumulateProjects(theSections, {})
my handleProjects(matchedProjects)
end tell
end tell

(*
Recurses over the given sections of the Library. Accumulates a list of projects that satisfy the 'shouldProjectBeIncluded' handler.

theSections: a list of folders, projects, and tasks
accum: the matching projects that have been found so far
*)
on accumulateProjects(theSections, accum)
if (theSections is {}) then return accum
return accumulateProjects(rest of theSections, accumulateProjectsHelper(item 1 of theSections, accum))
end accumulateProjects

(*
Recurses over the tree rooted at the given item. Accumulates a list of projects that satisfy the 'shouldProjectBeIncluded' handler.

theItem: a folder, project, or task
accum: the matching projects that have been found so far
*)
on accumulateProjectsHelper(theItem, accum)
using terms from application "OmniFocus"
if (class of theItem is project) then
-- screens the item before accumulating it
if my shouldProjectBeIncluded(theItem) then
set end of accum to theItem
end if
return accum
else if (class of theItem is folder) then
return my accumulateProjectsInFolder(theItem, accum)
else
(* The item must be a task. Shouldn't really reach here since we don't recurse into projects or SALs. *)
error "Script attempted to process inside a project. This script is supposedly designed to just process projects. So, it looks like there's a bug. Please contact the developer. We apologize for the inconvenience."
end if
end using terms from
end accumulateProjectsHelper

(*
Recurses over the tree rooted at the given folder. Accumulates a list of projects that satisfy the 'shouldProjectBeIncluded' handler.

theFolder: a folder
accum: the items lacking next actions that have been found so far
*)
on accumulateProjectsInFolder(theFolder, accum)
using terms from application "OmniFocus"
if ((not shouldLookInHiddenFolders) and hidden of theFolder) then return accum
set theChildren to every section of theFolder
return my accumulateProjects(theChildren, accum)
end using terms from
end accumulateProjectsInFolder
[/CODE]

All the customization points are near the top of the script. Search for "TODO". Feel free to use as you see fit, but please respect the attribution requirement in the license agreement.

Share and enjoy.

fudster 2010-02-10 05:49 PM

Sweet. Absolutely freakin' sweet.

Can't thank you enough Curt. This is exactly what I needed.

Noticed your comment/suggestion about summing a count - this was on my maybe/someday too, i.e. to have an easy visibility of how many active projects I'm managing, per DA's guideline of 30-100. Think I'll knock this one off tonight! I think I'm going to have several uses for this template. ;-)

Thanks again man, cheers.

fudster 2010-02-10 09:12 PM

I completed my Reset Review Dates script tonight using Curt's template and it works well.

Interestingly enough, the script won't run if I launch it from the scripts menu icon - it only runs when launching it from a toolbar button, or from the Play button in Applescript Editor.

I also create a script that counts my active and onhold projects. Here's the code I put in the handleProjects snippet:
[CODE]
set activeProjectCount to 0
set onholdProjectCount to 0
repeat with theProject in theMatchedProjects
if status of theProject is active then
set activeProjectCount to activeProjectCount + 1
end if

if status of theProject is on hold then
set onholdProjectCount to onholdProjectCount + 1
end if
end repeat
display dialog ((activeProjectCount as string) & " active projects
" & (onholdProjectCount as string) & " onhold projects")
[/CODE]

One more thing I'd like to do here - I'd also like the counts of my Stalled and Pending projects (using the terms from the sidebar filter). But of course, these are not actual statuses for the project, they are derived by OmniFocus. I'm out of time for tonight, but I think Curts's NextActions script would have the logic for the Stalled count... I'll see if I can shoehorn it in later on. (Obviously a simple look at the start date will do it for the Pending.)

curt.clifton 2010-02-11 05:05 AM

Strange about the script menu. It works from the FastScripts menu for me. I don't have the regular AppleScript menu.

fudster 2010-02-11 07:26 AM

Yeah, it is strange. I can't run it from the AppleScript menu or the FastScripts menu. More precisely, I suspect that it is running, but not finishing in a timely manner, so it appeared like it wasn't running it all. This morning after waking my mbp17, when I clicked on the FastScripts menu, my dialog finally showed, displaying the text from an older (i.e. mid-evening) version of the script. FastScripts also showed one sole menu item: "Terminate (or stop) running script". I think it might have been running 2 or 3 instances of the script.

curt.clifton 2010-02-11 09:03 AM

Ah, yes. With a complex database (lots of folders and projects), the necessary recursion can be slow. Looping over the matched projects is also slow.

If you're willing to split your script into multiple separate ones, it would be much faster to use the [I]shouldProjectBeIncluded[/I] method to just select a single project kind (e.g., on hold), then just display the [I]count of theMatchedProjects[/I] in [I]handleProjects[/I].

fudster 2010-02-11 09:33 AM

Ah, got it - makes sense. Sure, I don't mind splitting it. Thanks again Curt, cheers.

RobTrew 2010-02-11 10:42 PM

[QUOTE=curt.clifton;73133]With a complex database (lots of folders and projects), the necessary recursion can be slow.[/QUOTE]

You can get a flattened project list a little faster (and perhaps a bit more simply) by using [B]where[/B] queries.

e.g.

[CODE]property pfSkipHiddenFolders : false

on run
tell application id "com.omnigroup.OmniFocus"
set oDoc to front document
set lstAllProjects to my ProjectList(oDoc)
end tell
end run

on ProjectList(oParent)
using terms from application "OmniFocus"
tell oParent
set lstProject to (sections where class is project)
-- OR e.g. (sections where class is project and flagged is true)

if pfSkipHiddenFolders then
set lstFolder to (sections where class is folder and hidden is false)
else
set lstFolder to (sections where class is folder)
end if
if length of lstFolder > 0 then
return lstProject & my FolderProjects(lstFolder)
else
return lstProject
end if
end tell
end using terms from
end ProjectList

on FolderProjects(lstFolder)
set lstProject to {}
repeat with oFolder in lstFolder
set lstProject to lstProject & ProjectList(oFolder)
end repeat
return lstProject
end FolderProjects[/CODE]

[COLOR="White"]--[/COLOR]


All times are GMT -8. The time now is 11:14 PM.

Powered by vBulletin® Version 3.8.7
Copyright ©2000 - 2024, vBulletin Solutions, Inc.