Created
March 6, 2015 16:43
-
-
Save poritsky/d47ed16305b214b29849 to your computer and use it in GitHub Desktop.
Small modification to an old version of Chris Suave's Templates.scpt http://cmsauve.com/projects/templates/
This file contains hidden or 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
(* | |
Modified by Jonathan Poritsky in the smallest way: | |
March 6, 2015: Switched from Growl to OS X Notifications | |
Created and since updated by Chris Suave. Full Details at: | |
http://cmsauve.com/projects/templates/ | |
For whatever reason the lasest version isn't working as well as this two year old version (save for the growl to notifications change I made). | |
*) | |
(* | |
# DESCRIPTION | |
This script looks for a Template folder and asks you to pick one of the templates within to create | |
an instance of. It then allows you to select a folder for the instance and to choose the desired values | |
for any variables designated in the last paragraph of the project's note (by default, "$" before a word | |
designates it as a variable, an ode to my favorite CSS preprocessor, SASS). | |
Some other goodies: | |
- You can specify relative start or due dates (one or the other, not both… yet) for the project | |
and/or each task individually. To do so, put a paragraph somewhere in the note of that item that | |
starts with either "Due" or "Start". Then use the same natural language date syntax as you can usually | |
use in OmniFocus/ my "Later" Applescript: things like "today + 2d", "2d", "3w 4d 2pm", etc., will all | |
work as expected, hopefully. All of these will be relative to the date you run the script. | |
- Variables can be used in project names and notes, task names and notes, and task context names | |
- On the first run it detects if you have projects using Curt Clifton's OmniFocus template syntax | |
(to which I owe a lot of the good ideas in this script) and offers to change it for you. | |
- It offers to show you the new instance of the template once it has populated the instance. | |
- Automagically changes the new project instance to "Active" if the template project was "On Hold" | |
# LICENSE | |
Use it, change it, enjoy it. Please don't blatently pass off my work as your own. Be cool. | |
# INSTALLATION | |
- Copy this script to ~/Library/Scripts/Applications/Omnifocus (you may have to use the | |
Go > Go to Folder… menu in your file navigation application of choice as the user library | |
folder is hidden on Mac OS X 10.7+. After you select this menu item, type the path above and | |
hit enter). | |
- If you prefer, you can have this script be activated by a utility like Keyboard Maestro or FastScripts | |
# VERSION INFORMATION | |
0.4.0 (March 31, 2013): Added conditional task completion/ deletion | |
0.3.6 (March 27, 2013): Bugfixes. You can also now specify a specific folder path as the default folder using > for a subfolder | |
(i.e., ">>>Folder > Subfolder" will put the new instance in Subfolder under Folder). | |
0.3.5 (March 18, 2013): Added the ability to set dates in the format specified in as the short date format | |
in your Languages and Text preference pane. | |
0.3.1 (February 28, 2013): Bugfixes. | |
0.3.0 (February 24, 2013): Fixed an issue with subtracting dates. Improved Growl alerts. Added an option to put "attachment: ask" in | |
the task notes to have the script ask you for an attachment to that task. Variables can now be given a list of values to choose from | |
using the notation $variableName {option 1, option 2, option3} in the project note. If you use the variable "$today", the | |
variable will automatically be assigned the date you create the new instance. Added a screencast, readme, and website. | |
0.2.9 (February 13, 2013): Preserves non-embedded attachments to tasks | |
0.2.8 (February 13, 2013): Fixed the compile-time option of putting the project at the beginning of the list. | |
Changed notifications over to Growl. You can also have the script ask if a certain task should be completed or | |
not by putting "complete: ask" anywhere in its note. | |
0.2.7 (February 11, 2013): Fixed an issue where the template wouldn't instantiate properly if there were no variables. | |
Added a compile-time option to put the project at the beginning of the project list | |
0.2.6 (February 7, 2013): Now works with template folders that are dropped. | |
0.2.5 (January 30, 2013): New "$date" variables — will ask you for a date instead of a string (you can use all of the | |
same relative/ absolute shorthand forms in defining the date, and it can be used in conjunction with the "start" / | |
"due" identifiers) | |
0.2.4 (January 22, 2013): Other bugfixes | |
0.2.3 (January 22, 2013): Fix for setting default folder to a subfolder | |
0.2.2 (January 22, 2013): Allows you to set both a start and due date. Fixes a bug where due/ start declarations | |
in projects wouldn't be eliminated when a new instance was created. | |
0.2.1 (January 22, 2013): Does a better job of cleaning up notes and allows variables on any line of project | |
0.2 (January 21, 2013): using the keyword "ask" in after the start/due declaration in the note of a task/ project will have the | |
script prompt you to enter a relative or absolute start/due date for that item. Similarly, you can use the keyword | |
"project" to set the start/ due date relative to that of the project; the script will take whatever is | |
after the keyword and subtract it from the due date/ add it to the start date of the project, as the | |
case may be. Finally, using the (by default) ">>>" operator in the second, followed by a string that EXACTLY matches | |
one of the folders in your OF library will skip the folder selection dialog and put the new instance | |
directly in the designated folder. Plus, fancy icon. | |
0.1.1 (January 18, 2013): Handles projects in a template folder without variables more gracefully (thanks, Sven!) | |
0.1 (January 18, 2013): Initial release | |
# BEING WORKED ON | |
- Nothing | |
# KNOWN ISSUES | |
- Nothing | |
*) | |
property startOrEndOfFolder : "start" -- change to "end" to put the new project at the end of the selected folder | |
property variableSymbol : "$" -- change to whatever delimiter you want to denote your variables | |
property defaultFolderPointer : ">>>" -- change to whatever delimtier you want to denote a default folder pointer | |
property optionListStartDelimiter : "{" -- start of a list of options for the preceeding variable | |
property optionListEndDelimiter : "}" -- end of a list of options for the preceeding variable | |
-- Don't change these | |
property firstRun : true | |
property specialTemplateFolder : null | |
-- Growl variables, don't change these either | |
property applicationName : "Templates.scpt" | |
property scriptStartNotify : "Script Started" | |
property scriptEndNotify : "Script Ended" | |
property dateNotify : "Date Mismatch" | |
property allNotifications : {scriptStartNotify, scriptEndNotify, dateNotify} | |
property defaultNotifications : allNotifications | |
property iconApplication : "OmniFocus.app" | |
property checkingSomething : "" | |
tell application "OmniFocus" | |
tell default document | |
if firstRun then | |
set otherTemplateScriptProjects to my checkForOtherTemplate() | |
if otherTemplateScriptProjects is -1 then return | |
if length of otherTemplateScriptProjects is not 0 then | |
set changeOldTemplates to button returned of (display dialog "It appears that you have some projects using Curt Clifton's OmniFocus template script syntax. Would you like to automatically adjust these to use this script's syntax?" buttons {"No, Thanks", "Yes, Change Syntax"} default button 2) | |
if changeOldTemplates is "Yes, Change Syntax" then my adjustOldTemplateSyntax(otherTemplateScriptProjects) | |
end if | |
try | |
set variableSymbol to text returned of (display dialog "What symbol would you like to use to designate variable names in your templates?" default answer "$") | |
on error errorText number errorNumber | |
if errorNumber is -128 then | |
return | |
end if | |
end try | |
set firstRun to false | |
end if | |
set theCount to (count of (every flattened folder where (its name contains "Template"))) | |
if ((count of (every flattened folder where (its name contains "Template"))) is 0) then | |
set templateFolderList to every flattened folder where (its hidden is false) | |
set templateFolderNameList to {} | |
repeat with theFolder in templateFolderList | |
set nextListItem to "" | |
if the class of theFolder's container is folder then set nextListItem to "↳ " | |
set nextListItem to nextListItem & (name of theFolder) | |
set the end of templateFolderNameList to nextListItem | |
end repeat | |
set selectedTemplateFolder to choose from list templateFolderNameList with title "Choose Template Folder" with prompt "No obvious template folders were found. Please select the folder in which you store templates." OK button name "Set as Template Folder" | |
if selectedTemplateFolder is false then return | |
set templateFolderPosition to my selectionPositions(selectedTemplateFolder, templateFolderNameList, false) | |
set specialTemplateFolder to item templateFolderPosition of templateFolderList | |
else | |
set specialTemplateFolder to null | |
end if | |
if specialTemplateFolder is null then | |
set projectList to every flattened project where (name of its folder contains "Template") and (its status is not dropped) and (its status is not done) | |
else | |
set projectList to every flattened project where (its folder is specialTemplateFolder) and (its status is not dropped) and (its status is not done) | |
end if | |
set projectNameList to {} | |
repeat with theProject in projectList | |
set the end of projectNameList to the name of theProject | |
end repeat | |
if length of projectNameList is 0 then | |
display alert "No projects in a \"Template\" folder were found. Make sure that the folder name contains the word \"Template\" so that it can be found." | |
return | |
end if | |
set chooseListTitle to "Select a Template Project" | |
set chooseListText to "Which one of your template projects would you like to make a new instance of?" | |
set chooseListOK to "Select This Project" | |
set selectedProject to choose from list projectNameList with title chooseListTitle with prompt chooseListText OK button name chooseListOK | |
if selectedProject is false then return | |
set projectPosition to my selectionPositions(selectedProject, projectNameList, false) | |
set selectedProjectTemplate to item projectPosition of projectList | |
set defaultFolderFound to false | |
if the note of selectedProjectTemplate contains defaultFolderPointer then | |
set paraWithPointer to 1 | |
repeat with i from (count of paragraphs in the note of selectedProjectTemplate) to 1 by -1 | |
if (paragraph i of the note of selectedProjectTemplate starts with defaultFolderPointer) then set paraWithPointer to i | |
end repeat | |
set folderPointer to paragraph paraWithPointer of the note of selectedProjectTemplate | |
set my text item delimiters to {">>> ", ">>>", " > ", " >", "> ", ">"} | |
set newFolderText to every text item of folderPointer | |
set my text item delimiters to "" | |
set cleanedFolderText to {} | |
repeat with i from 1 to length of newFolderText | |
if item i of newFolderText is not "" then | |
set the end of cleanedFolderText to (paragraph 1 of (item i of newFolderText as string)) | |
end if | |
end repeat | |
try | |
if length of cleanedFolderText is 1 then | |
set selectedFolderTemplate to first flattened folder whose (name is item 1 of cleanedFolderText) | |
else | |
set theFolder to every flattened folder where (its name is item -1 of cleanedFolderText) | |
repeat with i from 1 to length of theFolder | |
set containFolder to container of item i of theFolder | |
if name of containFolder is (item -2 of cleanedFolderText) then | |
set selectedFolderTemplate to item i of theFolder | |
exit repeat | |
end if | |
end repeat | |
end if | |
set defaultFolderFound to true | |
end try | |
end if | |
set theVariables to item 1 of my findTheVariables(selectedProjectTemplate) | |
set theListVariableOptions to item 2 of my findTheVariables(selectedProjectTemplate) | |
set justDuplicate to "" | |
if the length of theVariables is 0 then | |
set justDuplicate to button returned of (display alert "No variables were found in the selected project. Do you want to simply copy this project to the selected folder? (Make sure that any variables are all in one paragraph in the project's note and are in the format: " & quote & variableSymbol & "variableName" & quote & ")." buttons {"No, Nevermind", "Yes, Duplicate"} default button 2) | |
if justDuplicate is not "Yes, Duplicate" then return | |
end if | |
if not defaultFolderFound then | |
set folderList to every flattened folder where (its name does not contain "Template") and (its effectively hidden is false) | |
set folderNameList to {"(Top Level)"} | |
repeat with theFolder in folderList | |
set nextListItem to "" | |
if the class of theFolder's container is folder then set nextListItem to "↳ " | |
set nextListItem to nextListItem & (name of theFolder) | |
set the end of folderNameList to nextListItem | |
end repeat | |
set chooseListTitle to "Select a Folder For The New Template Instance" | |
set chooseListText to "In which folder would you like to create a new instance of this template?" | |
set chooseListOK to "Make Template Instance" | |
set selectedFolder to choose from list folderNameList with title chooseListTitle with prompt chooseListText OK button name chooseListOK | |
if selectedFolder is false then | |
return | |
else if selectedFolder is {"(Top Level)"} then | |
set selectedFolderTemplate to "Top Level" | |
else | |
set folderPosition to ((my selectionPositions(selectedFolder, folderNameList, false)) - 1) | |
set selectedFolderTemplate to item folderPosition of folderList | |
end if | |
end if | |
if justDuplicate is "Yes, Duplicate" then | |
if selectedFolderTemplate is "Top Level" then | |
if startOrEndOfFolder is "start" then | |
set newProjectInstance to (duplicate selectedProjectTemplate to the front of projects of it) | |
else | |
set newProjectInstance to (duplicate selectedProjectTemplate to the end of projects of it) | |
end if | |
else | |
if startOrEndOfFolder is "start" then | |
set newProjectInstance to (duplicate selectedProjectTemplate to the front of projects of selectedFolderTemplate) | |
else | |
set newProjectInstance to (duplicate selectedProjectTemplate to the end of projects of selectedFolderTemplate) | |
end if | |
end if | |
display notification "Project \"" & (name of newProjectInstance) & "\" is ready for action!" with title "OmniFocus Templates" subtitle "Created New Template Instance" | |
my populateTemplate(newProjectInstance, {}, {}) | |
if (status of newProjectInstance is on hold) or (status of newProjectInstance is dropped) then set status of newProjectInstance to active | |
set theURL to "omnifocus:///task/" & (id of newProjectInstance) | |
set justDuplicate to "" | |
try | |
synchronize | |
end try | |
return | |
end if | |
my notify("Creating New Template Instance", "More input may be needed…", scriptStartNotify, "") | |
set theReplacements to my findTheReplacements(theVariables, theListVariableOptions) | |
if the result is false then return | |
if selectedFolderTemplate is "Top Level" then | |
if startOrEndOfFolder is "start" then | |
set newProjectInstance to (duplicate selectedProjectTemplate to the front of projects of it) | |
else | |
set newProjectInstance to (duplicate selectedProjectTemplate to the end of projects of it) | |
end if | |
else | |
if startOrEndOfFolder is "start" then | |
set newProjectInstance to (duplicate selectedProjectTemplate to the front of projects of selectedFolderTemplate) | |
else | |
set newProjectInstance to (duplicate selectedProjectTemplate to the end of projects of selectedFolderTemplate) | |
end if | |
end if | |
if status of newProjectInstance is on hold then set status of newProjectInstance to active | |
if defaultFolderFound then | |
copy the note of newProjectInstance to tempNote | |
repeat with i from (count of paragraphs in the note of selectedProjectTemplate) to 1 by -1 | |
if paragraph i of tempNote starts with defaultFolderPointer then set paraWithPointer to i | |
exit repeat | |
end repeat | |
set my text item delimiters to {return} | |
if paraWithPointer is 1 then | |
set the newNote to paragraphs 2 thru -1 of tempNote as string | |
else if paraWithPointer is (count of paragraphs of note of selectedProjectTemplate) then | |
set the newNote to paragraphs 1 thru -2 of tempNote as string | |
else | |
set the newNote to ((paragraphs 1 thru (paraWithPointer - 1) of tempNote) & (paragraphs (paraWithPointer + 1) thru -1 of tempNote)) as string | |
end if | |
set my text item delimiters to "" | |
set the note of newProjectInstance to newNote | |
end if | |
set theAttachments to my populateTemplate(newProjectInstance, theVariables, theReplacements) | |
set theURL to "omnifocus:///task/" & (id of newProjectInstance) | |
my notify("Script ended", "Click here to see the new project instance.", scriptEndNotify, theURL) | |
try | |
synchronize | |
end try | |
end tell | |
end tell | |
on selectionPositions(selectList, originalList, multipleSelections) | |
if multipleSelections then | |
set choicesFound to 0 | |
set positionOfSelections to {} | |
set j to 1 | |
repeat until (j > (length of originalList)) or (choicesFound = (length of selectList)) | |
set k to 1 | |
set aChoiceFound to false | |
repeat until (k > (length of selectList)) or aChoiceFound | |
if (item k of selectList) contains (item j of originalList) then | |
set end of positionOfSelections to j | |
set aChoiceFound to true | |
set choicesFound to (choicesFound + 1) | |
end if | |
set k to k + 1 | |
end repeat | |
set j to j + 1 | |
end repeat | |
else | |
set j to 1 | |
set positionOfSelections to null | |
repeat until (j > (length of originalList) or (positionOfSelections is not null)) | |
set k to 1 | |
repeat until ((k > (length of selectList)) or (positionOfSelections is not null)) | |
if (item k of selectList) contains (item j of originalList) then | |
set positionOfSelections to j | |
end if | |
set k to k + 1 | |
end repeat | |
set j to j + 1 | |
end repeat | |
end if | |
return positionOfSelections | |
end selectionPositions | |
on populateTemplate(theProject, cleanedVariables, theReplacements) | |
set delimCleanedVariables to {} | |
repeat with i from 1 to (length of cleanedVariables) | |
set the end of delimCleanedVariables to (variableSymbol & (item i of cleanedVariables)) | |
end repeat | |
tell application "OmniFocus" | |
tell default document | |
tell theProject | |
set theAttachmentList to my attachmentList(id of it, class of it as string) | |
if length of cleanedVariables > 0 then | |
set its name to my replaceVariables(its name, delimCleanedVariables, theReplacements) | |
end if | |
set possibleDateChange to {""} | |
repeat while item 1 of possibleDateChange is not missing value | |
set possibleDateChange to my checkingForDateInformation(it, delimCleanedVariables, theReplacements) | |
if item 1 of possibleDateChange is not missing value then | |
if item 2 of possibleDateChange is "Start" then | |
set its defer date to item 1 of possibleDateChange | |
else | |
set its due date to item 1 of possibleDateChange | |
end if | |
set its note to item 3 of possibleDateChange | |
else if the length of possibleDateChange is 2 then | |
set its note to item 2 of possibleDateChange | |
end if | |
end repeat | |
if length of cleanedVariables > 0 then | |
set theFullNote to its note | |
set theNewNote to my eliminateVariables(theFullNote) | |
set its note to my replaceVariables(theNewNote, delimCleanedVariables, theReplacements) | |
end if | |
set attachmentRequest to false | |
if its note contains "attachment: ask" or its note contains "attachment:ask" then | |
set attachmentRequest to true | |
set its note to my replaceVariables(its note, {"attachment:ask", "attachment: ask"}, {"", ""}) | |
end if | |
set its note to my cleanExcessBreaks(its note) | |
if attachmentRequest then | |
try | |
set theAttachFilePathAlias to (choose file with prompt "You indicated you would like to attach a file to the project \"" & name of it & "\". Please select the file to attach.") as text | |
tell its note | |
make new file attachment with properties {file name:theAttachFilePathAlias, embedded:false} | |
end tell | |
end try | |
end if | |
tell its note | |
repeat with theAttachment in theAttachmentList | |
make new file attachment with properties {file name:theAttachment, embedded:false} | |
end repeat | |
end tell | |
set attachmentRequest to false | |
-- Going through the tasks | |
repeat with i from 1 to (count of flattened tasks in it) | |
tell flattened task i | |
set theAttachmentList to my attachmentList(id of it, class of it as string) | |
if length of cleanedVariables > 0 then | |
set its name to my replaceVariables(its name, delimCleanedVariables, theReplacements) | |
end if | |
set completeTheTask to false | |
if its note contains "complete:ask" or its note contains "complete: ask" then | |
set completeTheTask to (button returned of (display dialog "In the note, you indicated that you wanted to be asked whether to complete the task \"" & (name of it) & "\" when you create a new instance of this project. Would you like to complete this task?" buttons {"Yes, Complete", "No, Leave Incomplete"} default button 2) is "Yes, Complete") | |
end if | |
if completeTheTask then | |
set its completed to true | |
else | |
set its note to my replaceVariables(its note, {"complete:ask", "complete: ask"}, {"", ""}) | |
set possibleDateChange to {""} | |
repeat while item 1 of possibleDateChange is not missing value | |
set possibleDateChange to my checkingForDateInformation(it, delimCleanedVariables, theReplacements) | |
if item 1 of possibleDateChange is not missing value then | |
if item 2 of possibleDateChange is "Start" then | |
set its defer date to item 1 of possibleDateChange | |
else | |
set its due date to item 1 of possibleDateChange | |
end if | |
set its note to item 3 of possibleDateChange | |
else if the length of possibleDateChange is 2 then | |
set its note to item 2 of possibleDateChange | |
end if | |
end repeat | |
if length of cleanedVariables > 0 then | |
my conditionalCheck(it, cleanedVariables, theReplacements) | |
set its note to my replaceVariables(its note, delimCleanedVariables, theReplacements) | |
if its context is not missing value then | |
set its context to my workingTheContext(its context, delimCleanedVariables, theReplacements) | |
end if | |
end if | |
set attachmentRequest to false | |
if its note contains "attachment: ask" or its note contains "attachment:ask" then | |
set attachmentRequest to true | |
set its note to my replaceVariables(its note, {"attachment:ask", "attachment: ask"}, {"", ""}) | |
end if | |
set its note to my cleanExcessBreaks(its note) | |
if attachmentRequest then | |
try | |
set theAttachFilePathAlias to (choose file with prompt "You indicated you would like to attach a file to the task \"" & name of it & "\". Please select the file to attach.") as text | |
tell its note | |
make new file attachment with properties {file name:theAttachFilePathAlias, embedded:false} | |
end tell | |
end try | |
end if | |
tell its note | |
repeat with theAttachment in theAttachmentList | |
make new file attachment with properties {file name:theAttachment, embedded:false} | |
end repeat | |
end tell | |
set attachmentRequest to false | |
end if | |
set completeTheTask to false | |
end tell | |
end repeat | |
set taskList to every flattened task of it | |
repeat with i from (length of taskList) to 1 by -1 | |
if note of (item i of taskList) contains "!!!Delete" then delete (item i of taskList) | |
end repeat | |
return theAttachmentList | |
end tell | |
end tell | |
end tell | |
end populateTemplate | |
on replaceVariables(theText, theVariables, theReplacements) | |
if length of theVariables is 0 then return theText | |
repeat with i from 1 to (length of theVariables) | |
set my text item delimiters to (item i of theVariables) | |
set theText to every text item of theText | |
if class of (item i of theReplacements) is date then | |
set my text item delimiters to (date string of (item i of theReplacements)) as text | |
else | |
set my text item delimiters to (item i of theReplacements) | |
end if | |
set theText to theText as string | |
set my text item delimiters to {} | |
end repeat | |
return theText | |
end replaceVariables | |
on eliminateVariables(theNote) | |
if (count of paragraphs of theNote) is 1 then | |
return "" | |
else | |
repeat with i from (count of paragraphs of theNote) to 1 by -1 | |
if paragraph i of theNote starts with variableSymbol then | |
set variablePosition to i | |
exit repeat | |
end if | |
end repeat | |
set my text item delimiters to {return} | |
if variablePosition is (count of the paragraphs of theNote) then | |
set returnNote to ((paragraphs 1 thru -2) of theNote) as text | |
else if variablePosition is 1 then | |
set returnNote to ((paragraphs 2 thru -1 of theNote)) as text | |
else | |
set returnNote to ((paragraphs 1 thru (variablePosition - 1) of theNote) & (paragraphs (variablePosition + 1) thru -1 of theNote)) as text | |
end if | |
set my text item delimiters to "" | |
return returnNote | |
end if | |
end eliminateVariables | |
on workingTheContext(theContext, theVariables, theReplacements) | |
tell application "OmniFocus" | |
if theContext is missing value then | |
return | |
else | |
set i to 1 | |
set variableFound to false | |
repeat while (i ≤ (length of theVariables)) and (not variableFound) | |
if (name of theContext contains (item i of theVariables)) then set variableFound to true | |
set i to i + 1 | |
end repeat | |
if not variableFound then | |
return theContext | |
else | |
set desiredContextName to my replaceVariables(name of theContext, theVariables, theReplacements) | |
if (class of (container of theContext) is document) then | |
set contextsInFolder to every context of default document | |
else | |
set contextsInFolder to every context in (container of theContext) | |
end if | |
set positionOfFound to null | |
set namesOfContextsInFolder to {} | |
repeat with i from 1 to (length of contextsInFolder) | |
set end of namesOfContextsInFolder to name of (item i of contextsInFolder) | |
if (item i of namesOfContextsInFolder) is desiredContextName then set positionOfFound to i | |
end repeat | |
if positionOfFound is not null then | |
return (item positionOfFound of contextsInFolder) | |
else | |
set theContainer to the container of theContext | |
tell theContainer | |
set newContext to make new context at the end of contexts of it with properties {name:desiredContextName} | |
end tell | |
return newContext | |
end if | |
end if | |
end if | |
end tell | |
end workingTheContext | |
on findTheVariables(theProject) | |
tell application "OmniFocus" | |
tell default document | |
set theFullNote to note of theProject | |
if theFullNote is missing value then return {{}, {}} | |
set theNote to null | |
repeat with i from (count of paragraphs of theFullNote) to 1 by -1 | |
if paragraph i of theFullNote starts with variableSymbol then | |
set theNote to paragraph i of theFullNote | |
exit repeat | |
end if | |
end repeat | |
if theNote is null then return {{}, {}} | |
set cleanedVariables to {} | |
set my text item delimiters to {" " & variableSymbol, variableSymbol} | |
set theVariables to every text item of theNote | |
repeat with theVar from 1 to (length of theVariables) | |
if item theVar of theVariables is not "" then | |
set the end of cleanedVariables to item theVar of theVariables | |
end if | |
end repeat | |
set optionLists to {} | |
repeat with i from 1 to length of cleanedVariables | |
if (item i of cleanedVariables contains optionListStartDelimiter) and (item i of cleanedVariables contains optionListEndDelimiter) then | |
set my text item delimiters to {space & optionListStartDelimiter & space, space & optionListEndDelimiter & space, space & optionListStartDelimiter, space & optionListEndDelimiter, optionListStartDelimiter, optionListEndDelimiter} | |
set theSplit to every text item of (item i of cleanedVariables) | |
set (item i of cleanedVariables) to (item 1 of theSplit) | |
set newOptionListText to item 2 of theSplit | |
set my text item delimiters to {", ", ","} | |
set newOptionList to every text item of newOptionListText | |
set emptyFound to true | |
repeat while emptyFound | |
repeat with j from 1 to length of newOptionList | |
set emptyFound to false | |
if item j of newOptionList is "" then | |
if j is 1 then | |
set newOptionList to items 2 thru -1 of newOptionList | |
else if j is length of newOptionList then | |
set newOptionList to items 1 thru -2 of newOptionList | |
else | |
set newOptionList to (items 1 thru (j - 1) of newOptionList) & (items (j + 1) thru -1 of newOptionList) | |
end if | |
set emptyFound to true | |
exit repeat | |
end if | |
end repeat | |
end repeat | |
set end of optionLists to newOptionList | |
else | |
set end of optionLists to {} | |
end if | |
end repeat | |
set my text item delimiters to {} | |
end tell | |
end tell | |
return {cleanedVariables, optionLists} | |
end findTheVariables | |
on findTheReplacements(theVariables, optionLists) | |
tell application "OmniFocus" | |
tell default document | |
set theReplacements to {} | |
set theTitle to "Select Replacements for Variables" | |
repeat with i from 1 to (length of theVariables) | |
if item i of theVariables contains "today" then | |
set the end of theReplacements to (current date) | |
else if item i of theVariables starts with "date" then | |
set theText to "What date would you like to use for the date variable " & quote & (item i of theVariables) & quote & "? You can use an absolute or relative date." | |
else if item i of optionLists is not {} then | |
set theText to "Which of the following options would you like to assign to the variable \"" & (item i of theVariables) & "\"?" | |
else | |
set theText to "What would you like to replace " & quote & (item i of theVariables) & quote & " with?" | |
end if | |
if item i of theVariables does not contain "today" then | |
try | |
if item i of optionLists is {} then | |
set theReturnInput to text returned of (display dialog theText default answer "") | |
if item i of theVariables starts with "date" then | |
set theReturnInput to my englishTime(theReturnInput) | |
set theCurrentDate to (current date) | |
set time of theCurrentDate to 0 | |
set theReturnInput to theCurrentDate + theReturnInput | |
end if | |
set the end of theReplacements to theReturnInput | |
else | |
set the end of theReplacements to (choose from list (item i of optionLists) with prompt theText) as string | |
end if | |
on error errorText number errorNumber | |
if errorNumber is -128 then | |
return false | |
end if | |
end try | |
end if | |
end repeat | |
end tell | |
end tell | |
return theReplacements | |
end findTheReplacements | |
on cleanExcessBreaks(theText) | |
if theText is missing value then return missing value | |
if theText is "" then return "" | |
if (count of paragraphs of theText) is 1 then return theText | |
repeat with i from (count of paragraphs of theText) to 1 by -1 | |
if paragraph i of theText is not "" then | |
set textEnds to i | |
exit repeat | |
end if | |
end repeat | |
repeat with j from 1 to (count of paragraphs of theText) | |
if paragraph j of theText is not "" then | |
set textStarts to j | |
exit repeat | |
end if | |
end repeat | |
set text item delimiters to {return} | |
set theNewText to paragraphs textStarts thru textEnds of theText as text | |
set text item delimiters to "" | |
return theNewText | |
end cleanExcessBreaks | |
on checkForOtherTemplate() | |
tell application "OmniFocus" | |
tell default document | |
set theCount to (count of (every flattened folder where (its name contains "Template"))) | |
if (theCount is 0) then | |
set templateFolderList to every flattened folder | |
set templateFolderNameList to {} | |
repeat with theFolder in templateFolderList | |
set nextListItem to "" | |
if the class of theFolder's container is folder then set nextListItem to "↳ " | |
set nextListItem to nextListItem & (name of theFolder) | |
set the end of templateFolderNameList to nextListItem | |
end repeat | |
set selectedTemplateFolder to choose from list templateFolderNameList with title "Choose Template Folder(s)" with prompt "No obvious template folders were found. Please select the folder(s) in which you store templates." OK button name "Set as Template Folder" with multiple selections allowed | |
if selectedTemplateFolder is false then return -1 | |
set templateFolderPosition to my selectionPositions(selectedTemplateFolder, templateFolderNameList, true) | |
set existingTemplateFolders to {} | |
repeat with i from 1 to (length of templateFolderPosition) | |
set the end of existingTemplateFolders to item i of templateFolderList | |
end repeat | |
else | |
set existingTemplateFolders to every flattened folder where (its name contains "Template") | |
end if | |
set oldTemplateProjects to {} | |
set possibleTemplateProjects to {} | |
repeat with i from 1 to (length of existingTemplateFolders) | |
set templateFolderIsDropped to (hidden of item i of existingTemplateFolders is true) | |
try | |
if not templateFolderIsDropped then | |
set possibleTemplateProjects to possibleTemplateProjects & (every flattened project where (its container is (item i of existingTemplateFolders)) and (its status is not dropped) and (its status is not done)) | |
else | |
set possibleTemplateProjects to possibleTemplateProjects & (every flattened project where (its container is (item i of existingTemplateFolders)) and (its status is not done)) | |
end if | |
end try | |
end repeat | |
repeat with i from 1 to (length of possibleTemplateProjects) | |
if the note of (item i of possibleTemplateProjects) contains "«" then set the end of oldTemplateProjects to (item i of possibleTemplateProjects) | |
end repeat | |
end tell | |
end tell | |
return oldTemplateProjects | |
end checkForOtherTemplate | |
on adjustOldTemplateSyntax(oldTemplateProjects) | |
tell application "OmniFocus" | |
tell default document | |
repeat with i from 1 to (length of oldTemplateProjects) | |
tell item i of oldTemplateProjects | |
set my text item delimiters to "«" | |
set tempName to its name | |
set tempName to (every text item of tempName) | |
set tempNote to its note | |
set tempNote to (every text item of tempNote) | |
set my text item delimiters to variableSymbol | |
set tempName to tempName as string | |
set tempNote to tempNote as string | |
set my text item delimiters to "»" | |
set tempName to (every text item of tempName) | |
set tempNote to (every text item of tempNote) | |
set my text item delimiters to "" | |
set its name to tempName as string | |
set its note to tempNote as string | |
repeat with i from 1 to (count of flattened tasks in it) | |
tell flattened task i | |
set my text item delimiters to "«" | |
set tempName to its name | |
set tempName to (every text item of tempName) | |
set tempNote to its note | |
set tempNote to (every text item of tempNote) | |
set my text item delimiters to variableSymbol | |
set tempName to tempName as string | |
set tempNote to tempNote as string | |
set my text item delimiters to "»" | |
set tempName to (every text item of tempName) | |
set tempNote to (every text item of tempNote) | |
set my text item delimiters to "" | |
set its name to tempName as string | |
set its note to tempNote as string | |
end tell | |
end repeat | |
end tell | |
end repeat | |
set my text item delimiters to "" | |
end tell | |
end tell | |
end adjustOldTemplateSyntax | |
on checkingForDateInformation(theItem, theVariables, theReplacements) | |
tell application "OmniFocus" | |
tell default document | |
tell front document window | |
tell content | |
set theNote to the note of theItem | |
copy theNote to theOriginalNote | |
set dueOrStart to null | |
set askForDate to false | |
set relativeToProject to false | |
set dateVariable to false | |
set theNoteParagraphs to every paragraph of theNote | |
repeat with i from 1 to (length of theNoteParagraphs) | |
if (item i of theNoteParagraphs starts with "start:") or (item i of theNoteParagraphs starts with "due:") then | |
set theNote to item i of theNoteParagraphs | |
exit repeat | |
end if | |
end repeat | |
if (theNote starts with "Due") then | |
set dueOrStart to "due" | |
else if (theNote starts with "Start") then | |
set dueOrStart to "start" | |
end if | |
if dueOrStart is null then | |
return {missing value} | |
else | |
if theNote contains "Ask" then set askForDate to true | |
if theNote contains "Project" then set relativeToProject to true | |
repeat with i from 1 to (length of theVariables) | |
if item i of theVariables is in theNote then | |
set dateVariable to true | |
set dateVariablePosition to i | |
exit repeat | |
end if | |
end repeat | |
if askForDate then | |
set classOfItem to "item" | |
if class of theItem is task then | |
set classOfItem to "task" | |
else if class of theItem is project then | |
set classOfItem to "project" | |
end if | |
set displayText to "When would you like the " & dueOrStart & " date of the " & classOfItem & " " & quote & (name of theItem) & quote & " to be? You can use relative (i.e., \"3d 2pm\"), absolute (i.e., \"Jan 19 15:00\"), or the short date format from your \"Language and Text\" preferences (i.e., \"13.01.19\" or \"01-19\") dates in your input." | |
try | |
set inputDate to text returned of (display dialog displayText default answer "1d 12am") | |
on error errorText number errorNumber | |
if errorNumber is -128 then | |
return {missing value, my getRidOfDateInfo(theOriginalNote, dueOrStart)} | |
end if | |
end try | |
else | |
set possibleDelimiters to {"Due:", "Start:", "Due", "Start", "Project", "today", "at"} | |
if dateVariable then set end of possibleDelimiters to (item dateVariablePosition of theVariables) as string | |
set my text item delimiters to possibleDelimiters | |
copy every text item of theNote to tempDate | |
set my text item delimiters to "" | |
set inputDate to tempDate as string | |
end if | |
end if | |
set secondsDeferred to my englishTime(inputDate) | |
if secondsDeferred = -1 then return {missing value, my getRidOfDateInfo(theOriginalNote, dueOrStart)} | |
if not dateVariable then | |
if not relativeToProject then | |
set desiredDate to (current date) | |
set time of desiredDate to 0 | |
set desiredDate to desiredDate + secondsDeferred | |
else | |
if class of theItem is project then return {missing value} | |
if dueOrStart is "due" then | |
set relativeDate to due date of containing project of theItem | |
if relativeDate is missing value then | |
return {missing value, my getRidOfDateInfo(theOriginalNote, dueOrStart)} | |
end if | |
set desiredDate to relativeDate + secondsDeferred | |
else | |
set relativeDate to defer date of containing project of theItem | |
if relativeDate is missing value then | |
set relativeDate to (current date) | |
set time of relativeDate to 0 | |
end if | |
set desiredDate to relativeDate + secondsDeferred | |
end if | |
end if | |
else | |
set desiredDate to (item dateVariablePosition of theReplacements) + secondsDeferred | |
end if | |
if (class of theItem is not project) then | |
if dueOrStart is "Due" and (due date of containing project of theItem is not missing value) then | |
if desiredDate < (current date) then | |
my notify("Due Date in the Past", "Check task \"" & name of theItem & "\".", dateNotify, "") | |
else if desiredDate > due date of containing project of theItem then | |
my notify("Due Date After Project Due", "Check task \"" & name of theItem & "\".", dateNotify, "") | |
end if | |
else if (due date of containing project of theItem is not missing value) then | |
if desiredDate > due date of containing project of theItem then | |
my notify("Start Date After Project Start", "Check task \"" & name of theItem & "\".", dateNotify, "") | |
end if | |
end if | |
end if | |
set theNote to my getRidOfDateInfo(theOriginalNote, dueOrStart) | |
return {desiredDate, dueOrStart, theNote} | |
end tell | |
end tell | |
end tell | |
end tell | |
end checkingForDateInformation | |
on englishTime(dateDesired) | |
if dateDesired is "0" then return 0 | |
set monthFound to 0 | |
set weekdayFound to 0 | |
-- Solves an issue with the treatment of leading zeros for the minutes (i.e., 12:01am) | |
set minuteLeadingZero to false | |
-- Figures out if the user excluded any of the components | |
set timeMissing to false | |
set daysMissing to false | |
set weeksMissing to false | |
-- Sets up the delimiters for different items | |
set timeDelimiters to {"am", "pm", "a", "p", ":"} | |
set dayDelimiters to {"days", "day", "d"} | |
set weekDelimiters to {"weeks", "week", "w"} | |
set monthDelimiters to {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"} | |
set weekdayDelimiters to {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"} | |
set specialRelativeDayDelimiters to {"Today", "Tomorrow", "at"} | |
set otherDelimiters to {" ", "th", "st", "rd", "nd"} | |
set inThe to "unknown" | |
set howManyNumbersInputted to 0 | |
set numList to {} | |
-- See if they included AM/PM | |
if isNumberIdentifier("a", dateDesired) then set inThe to "AM" | |
if isNumberIdentifier("p", dateDesired) then set inThe to "PM" | |
-- See if they gave an absolute date formatted in YY.MM.DD or some other similar format | |
set my text item delimiters to specialRelativeDayDelimiters & otherDelimiters & timeDelimiters | |
set checkInput to every text item of dateDesired | |
set checkInputCleaned to {} | |
repeat with i from 1 to (length of checkInput) | |
if item i of checkInput is not "" then | |
set the end of checkInputCleaned to item i of checkInput | |
end if | |
end repeat | |
try | |
set theDateCheck to item 1 of checkInputCleaned | |
if (theDateCheck contains ".") or (theDateCheck contains "-") or (theDateCheck contains "/") then | |
set todaysDate to (current date) | |
set time of todaysDate to 0 | |
set targetDate to my understandAbsoluteDate(theDateCheck) | |
if targetDate = -1 then return -1 | |
set my text item delimiters to "" | |
if length of checkInputCleaned is 1 then | |
return (targetDate - todaysDate) as number | |
else | |
set theTime to items 2 thru -1 of checkInputCleaned | |
set numList to {} | |
set timeStoreLocation to length of theTime | |
repeat while timeStoreLocation > 0 | |
try | |
-- If the minutes have a leading zero, just combine them with the hours | |
if (numList = {}) and ((item timeStoreLocation of theTime) starts with "0") then | |
set the end of numList to ((item (timeStoreLocation - 1) of theTime) & (item timeStoreLocation of theTime)) as number | |
set minuteLeadingZero to true | |
set timeStoreLocation to timeStoreLocation - 2 | |
else | |
-- Otherwise, get the numbers only | |
set tempNum to (item timeStoreLocation of theTime) as number | |
if tempNum ≠ 0 then set the end of numList to tempNum | |
set timeStoreLocation to timeStoreLocation - 1 | |
end if | |
end try | |
end repeat | |
set theTime to figureOutTheTime(numList, false, true, true, minuteLeadingZero) | |
set theTime to understandTheTime(theTime, inThe, false) | |
return (targetDate + theTime - todaysDate) as number | |
end if | |
end if | |
end try | |
-- See if they gave an absolute date, a relative one, or a day of the week | |
repeat with i from 1 to (length of monthDelimiters) | |
if dateDesired contains (item i of monthDelimiters) then | |
set monthFound to i | |
exit repeat | |
end if | |
if i ≤ (length of weekdayDelimiters) then | |
if dateDesired contains (item i of weekdayDelimiters) then | |
set weekdayFound to i | |
end if | |
end if | |
end repeat | |
-- Getting rid of all the bits I could imagine being around the numbers | |
set text item delimiters to (specialRelativeDayDelimiters & monthDelimiters & weekDelimiters & dayDelimiters & timeDelimiters & otherDelimiters) | |
set inputList to every text item of dateDesired | |
-- Resetting delimiters | |
set text item delimiters to {""} | |
repeat with i from 1 to (length of inputList) | |
if item i of inputList is "-" and (character 1 of item (i + 1) of inputList is in "123456789") then | |
set item (i + 1) of inputList to item i of inputList & item (i + 1) of inputList | |
end if | |
end repeat | |
-- Count how many numbers were given | |
repeat with i from 1 to (length of inputList) | |
if (item i of inputList) is not "" then | |
try | |
set tempItem to (item i of inputList) as integer | |
if class of tempItem is integer then set howManyNumbersInputted to howManyNumbersInputted + 1 | |
end try | |
end if | |
set tempItem to "" | |
end repeat | |
-- Get the numbers of the input — start from the back to get the minutes first | |
set timeStoreLocation to length of inputList | |
repeat while timeStoreLocation > 0 | |
try | |
-- If the minutes have a leading zero, just combine them with the hours | |
if (numList = {}) and ((item timeStoreLocation of inputList) starts with "0") then | |
set the end of numList to ((item (timeStoreLocation - 1) of inputList) & (item timeStoreLocation of inputList)) as number | |
set minuteLeadingZero to true | |
set timeStoreLocation to timeStoreLocation - 2 | |
else | |
-- Otherwise, get the numbers only | |
try | |
set tempNum to (item timeStoreLocation of inputList) as number | |
on error | |
set timeStoreLocation to timeStoreLocation - 1 | |
end try | |
if tempNum ≠ 0 then set the end of numList to tempNum | |
set timeStoreLocation to timeStoreLocation - 1 | |
end if | |
end try | |
end repeat | |
-- Reverse it so the order is from biggest to smallest time increment | |
set numList to reverse of numList | |
if (monthFound is 0) and (weekdayFound is 0) then | |
-- If the user gave a relative date... | |
tell dateDesired | |
set daysMissing to not my isNumberIdentifier("d", it) | |
set weeksMissing to not my isNumberIdentifier("w", it) | |
if (howManyNumbersInputted - ((not daysMissing) as integer) - ((not weeksMissing) as integer)) = 0 then set timeMissing to true | |
end tell | |
-- Figure out how many weeks | |
if not weeksMissing then | |
set weeksDeferred to item 1 of numList | |
else | |
set weeksDeferred to 0 | |
end if | |
-- Figure out how many days | |
if not daysMissing then | |
set daysDeferred to howManyDays(numList, weeksMissing) | |
else | |
if dateDesired contains "Tomorrow" then | |
-- Special case where they put "tomorrow" | |
set daysDeferred to 1 | |
else | |
-- If they exclude it entirely or put "Today" | |
set daysDeferred to 0 | |
end if | |
end if | |
-- Figure out the time | |
set timeDeferredTemp to figureOutTheTime(numList, timeMissing, daysMissing, weeksMissing, minuteLeadingZero) | |
-- Understand the meaning of the time component | |
set timeDeferred to understandTheTime(timeDeferredTemp, inThe, timeMissing) | |
-- Creating the time deferred based on minutes and hours calculated | |
if timeDeferred ≥ 0 then | |
set totalTimeDeferred to timeDeferred + daysDeferred * days + weeksDeferred * weeks | |
else | |
set totalTimeDeferred to timeDeferred | |
end if | |
-- end of relative date-only code | |
else if (weekdayFound > 0) and (monthFound is 0) then | |
if length of numList < 1 then set timeMissing to true | |
-- Same as if the day and the week were missing on a relative date | |
set timeDeferredTemp to figureOutTheTime(numList, timeMissing, true, true, minuteLeadingZero) | |
set timeDeferred to understandTheTime(timeDeferredTemp, inThe, timeMissing) | |
set daysDeferred to daysFromTodayToWeekday(weekdayFound) | |
if timeDeferred ≥ 0 then | |
set totalTimeDeferred to daysDeferred * days + timeDeferred | |
else | |
set totalTimeDeferred to timeDeferred | |
end if | |
else | |
-- If the user gave an absolute date... | |
if length of numList < 2 then set timeMissing to true | |
-- Same as if the day were there but week wasn't on a relative date | |
set timeDeferredTemp to figureOutTheTime(numList, timeMissing, false, true, minuteLeadingZero) | |
set timeDeferred to understandTheTime(timeDeferredTemp, inThe, timeMissing) | |
set timeFromTodayUntilDesired to figuringTimeToDesiredDay(monthFound, (item 1 of numList)) | |
if timeDeferred ≥ 0 then | |
set totalTimeDeferred to timeFromTodayUntilDesired + timeDeferred | |
else | |
set totalTimeDeferred to timeDeferred | |
end if | |
end if | |
return totalTimeDeferred | |
end englishTime | |
on isNumberIdentifier(possibleIdentifier, containerString) | |
set numberIdentifier to true | |
set identifierIsInContainer to false | |
set positionOfLastIdentifier to 0 | |
set charList to every character of containerString | |
repeat with i from 1 to (length of charList) | |
if (item i of charList) = possibleIdentifier then | |
set identifierIsInContainer to true | |
set positionOfLastIdentifier to i | |
end if | |
end repeat | |
if (positionOfLastIdentifier is 0) or (positionOfLastIdentifier is 1) then | |
set numberIdentifier to false | |
else | |
set characterBefore to character (positionOfLastIdentifier - 1) of containerString | |
try | |
set characterBefore to characterBefore as integer | |
end try | |
if (characterBefore is not " ") and (class of characterBefore is not integer) then set numberIdentifier to false | |
end if | |
return numberIdentifier | |
end isNumberIdentifier | |
on howManyDays(numList, weeksMissing) | |
if not weeksMissing then | |
set daysDeferred to item 2 of numList | |
else | |
set daysDeferred to item 1 of numList | |
end if | |
return daysDeferred | |
end howManyDays | |
on figureOutTheTime(numList, timeMissing, daysMissing, weeksMissing, minuteLeadingZero) | |
if not timeMissing then | |
if minuteLeadingZero then | |
set timeDeferredTemp to item -1 of numList | |
else | |
set text item delimiters to "" | |
set timeDeferredTemp to ((items -1 thru (1 + ((not daysMissing) as integer) + ¬ | |
((not weeksMissing) as integer)) of numList) as text) as integer | |
end if | |
else | |
set timeDeferredTemp to 0 | |
end if | |
return timeDeferredTemp | |
end figureOutTheTime | |
to understandTheTime(timeDeferredTemp, inThe, timeMissing) | |
if timeMissing then | |
set timeDeferred to 0 | |
else | |
if timeDeferredTemp > 2400 then | |
-- If the time is greater than the 24 hour clock... | |
display alert "Please try again: the time you entered was not a valid time of day." | |
set timeDeferred to -1 | |
else if timeDeferredTemp = 2400 then | |
-- If the time is equal to 2400... | |
set timeDeferred to days | |
else if timeDeferredTemp ≥ 100 then | |
-- if they entered the time as a full hour:minute pair (with or without AM/PM and with or without the colon) | |
set minutesDeferred to (((characters -2 thru -1 of (timeDeferredTemp as text)) as text) as integer) | |
set hoursDeferred to (((characters 1 thru -3 of (timeDeferredTemp as text)) as text) as integer) | |
-- Figuring out the minutes and hours in the time given (minutes are last two numbers) | |
if inThe = "PM" then | |
-- For any number specifically designated as PM | |
set timeDeferred to ((hoursDeferred + 12) * hours + minutesDeferred * minutes) | |
else if hoursDeferred = 12 and inThe = "AM" then | |
-- For 12:00AM exactly | |
set timeDeferred to minutesDeferred * minutes | |
else | |
-- For times in the AM (implicit or explicit) and explicit times in the PM (i.e., 16:00) | |
set timeDeferred to (hoursDeferred * hours + minutesDeferred * minutes) | |
end if | |
else if timeDeferredTemp > 24 then | |
-- If they entered the time as a single number above 24 | |
display alert "Please try again: the time you entered was not a valid time of day." | |
set timeDeferred to -1 | |
else if timeDeferredTemp ≤ 24 then | |
-- If the entered the time as a single number (with or without AM/PM) | |
if timeDeferredTemp = 24 then | |
-- If they entered 24 hours exactly (treat as a full extra delay) | |
set timeDeferred to days | |
else if (timeDeferredTemp = 12) and (inThe ≠ "AM") then | |
-- If they entered "12" (treat it as 12PM) | |
set timeDeferred to 12 * hours | |
else if (timeDeferredTemp ≥ 12) or (inThe ≠ "PM") then | |
-- For implicit and explicit AM entries and for implicit PM entries | |
set timeDeferred to timeDeferredTemp * hours | |
else | |
-- For explicit PM entries | |
set timeDeferred to (timeDeferredTemp + 12) * hours | |
end if | |
end if | |
end if | |
return timeDeferred | |
end understandTheTime | |
to figuringTimeToDesiredDay(monthDesired, dayDesired) | |
set todaysDate to (current date) | |
set time of todaysDate to 0 | |
-- Creating an intial date object | |
copy todaysDate to exactDesiredDate | |
set (day of exactDesiredDate) to dayDesired | |
set (month of exactDesiredDate) to monthDesired | |
if exactDesiredDate < (current date) then | |
set (year of exactDesiredDate) to ((year of todaysDate) + 1) | |
end if | |
return (exactDesiredDate - todaysDate) | |
end figuringTimeToDesiredDay | |
on daysFromTodayToWeekday(weekdayDesired) | |
set currentWeekday to (weekday of (current date)) as integer | |
if currentWeekday = weekdayDesired then | |
set daysDeferred to 0 | |
else if currentWeekday < weekdayDesired then | |
set daysDeferred to weekdayDesired - currentWeekday | |
else | |
set daysDeferred to 7 + weekdayDesired - currentWeekday | |
end if | |
return daysDeferred | |
end daysFromTodayToWeekday | |
on understandAbsoluteDate(theText) | |
set theDate to (current date) | |
set the day of theDate to 1 | |
set the month of theDate to 2 | |
set theDate to (theDate - 1 * days) | |
set theDate to short date string of theDate | |
set text item delimiters to {".", "-", "/", "–", "—", "|", "\\"} | |
set theDate to every text item of theDate | |
set thePositions to {theDay:0, theMonth:0, theYear:0} | |
-- Checks the positions of the date components based on January 31 of this year | |
repeat with i from 1 to (length of theDate) | |
tell item i of theDate | |
if it is in "01" then | |
set (theMonth in thePositions) to i | |
else if it is in "31" then | |
set (theDay in thePositions) to i | |
else | |
set (theYear in thePositions) to i | |
end if | |
end tell | |
end repeat | |
set theText to every text item of theText | |
set targetDate to (current date) | |
set time of targetDate to 0 | |
if (length of theText is not 2) and (length of theText is not 3) then | |
-- If they don't input at 2-3 numbers, return the error | |
return -1 | |
else | |
if length of theText is 3 then | |
-- If the input has three numbers | |
set the year of targetDate to solveTheYear((item (theYear of thePositions) of theText) as number) | |
else | |
-- If the input has two numbers (left out the year) | |
set thePositions to adjustPositionsForNoYear(thePositions) | |
end if | |
set the month of targetDate to (item (theMonth of thePositions) of theText) as number | |
set the day of targetDate to (item (theDay of thePositions) of theText) as number | |
if targetDate is less than (current date) then | |
set the year of targetDate to (the year of (current date)) + 1 | |
end if | |
end if | |
return targetDate | |
end understandAbsoluteDate | |
to adjustPositionsForNoYear(thePositions) | |
if (theYear in thePositions) is 1 then | |
set (theMonth in thePositions) to (theMonth in thePositions) - 1 | |
set (theDay in thePositions) to (theDay in thePositions) - 1 | |
else if yearPosition is 2 then | |
if (theDay in thePositions) < (theMonth in thePositions) then | |
set (theMonth in thePositions) to (theMonth in thePositions) - 1 | |
else | |
set (theDay in thePositions) to (theDay in thePositions) - 1 | |
end if | |
end if | |
return thePositions | |
end adjustPositionsForNoYear | |
to solveTheYear(num) | |
if num ≥ 1000 then | |
return num | |
else | |
return (2000 + num) | |
end if | |
end solveTheYear | |
on attachmentList(theID, theClass) | |
set theAttachmentList to {} | |
tell front document of application "OmniFocus" | |
if theClass is "task" then | |
tell note of task id theID | |
set NumberOfFileAttached to number of file attachment | |
set i to 1 | |
repeat while i ≤ NumberOfFileAttached | |
if file attachment i is not embedded then | |
set end of theAttachmentList to file name of file attachment i as string | |
end if | |
set i to i + 1 | |
end repeat | |
end tell | |
end if | |
if theClass is "project" then | |
tell note of project id theID | |
set NumberOfFileAttached to number of file attachment | |
set i to 1 | |
repeat while i ≤ NumberOfFileAttached | |
if file attachment i is not embedded then | |
set end of theAttachmentList to file name of file attachment i as string | |
end if | |
set i to i + 1 | |
end repeat | |
end tell | |
end if | |
end tell | |
return theAttachmentList | |
end attachmentList | |
on getRidOfDateInfo(theOriginalNote, dueOrStart) | |
set numParagraphs to (length of paragraphs of theOriginalNote) | |
repeat with i from 1 to numParagraphs | |
if paragraph i of theOriginalNote starts with dueOrStart then | |
set dateSpot to i | |
exit repeat | |
end if | |
end repeat | |
set my text item delimiters to {return} | |
if dateSpot is 1 then | |
if (count of paragraphs of theOriginalNote) is 1 then | |
set theNote to "" | |
else | |
set theNote to (paragraphs 2 thru -1 of the theOriginalNote) as string | |
end if | |
else if dateSpot is numParagraphs then | |
set theNote to ((paragraphs 1 thru -2) of theOriginalNote) as string | |
else | |
set theNote to (((paragraphs 1 thru (dateSpot - 1)) of theOriginalNote) & ((paragraphs (dateSpot + 1) thru -1) of theOriginalNote)) as string | |
end if | |
set my text item delimiters to "" | |
return theNote | |
end getRidOfDateInfo | |
on conditionalCheck(theTask, theVariables, theReplacements) | |
set theOperation to "" | |
set theFunction to "" | |
set variableValue to 5 | |
set operationDelimiters to {" delete", " complete", " defer", "delete", "complete", "defer", " by ", " by", "by ", "by"} | |
set functionDelimiters to {" <= ", "<= ", " <=", "<=", " ≤ ", "≤ ", " ≤", "≤", ¬ | |
" >= ", ">= ", " >=", ">=", " ≥ ", "≥ ", " ≥", "≥", ¬ | |
" == ", "== ", " ==", "==", ¬ | |
" != ", "!= ", " !=", "!=", ¬ | |
" > ", "> ", " >", ">", ¬ | |
" < ", "< ", " <", "<"} | |
set condition to false | |
tell application "OmniFocus" | |
tell content of first document window of front document | |
try | |
set theNote to note of theTask | |
on error | |
return | |
end try | |
if theNote contains "@if" then | |
repeat with i from 1 to length of paragraphs of theNote | |
if paragraph i of theNote starts with "@if" then | |
set paraPointer to i | |
set theNote to paragraph paraPointer of theNote | |
exit repeat | |
end if | |
end repeat | |
else | |
return | |
end if | |
set theOperation to my determineOperation(theNote) | |
set theFunction to my determineFunction(theNote) | |
if (theOperation is "") or (theFunction is "") then return | |
set my text item delimiters to {"@if ", "@if", " then ", "\"", "”", "“", variableSymbol} & operationDelimiters & functionDelimiters | |
copy every text item of theNote to notePieces | |
set my text item delimiters to "" | |
set notePieces to my clearEmpties(notePieces) | |
if (length of notePieces < 2) or (length of notePieces > 3) then return false | |
if (item i of notePieces as list) is not in theVariables then return | |
set variableValue to false | |
repeat with i from 1 to length of theVariables | |
if item i of theVariables is (item 1 of notePieces) then | |
set variableValue to item i of theReplacements | |
exit repeat | |
end if | |
end repeat | |
if variableValue is false then | |
my clearNote(theTask, paraPointer) | |
return | |
end if | |
try | |
set variableValue to variableValue as number | |
end try | |
try | |
if (theFunction is ">=") and (variableValue ≥ (item 2 of notePieces) as number) then | |
set condition to true | |
else if (theFunction is "<=") and (variableValue ≤ (item 2 of notePieces) as number) then | |
set condition to true | |
else if (theFunction is ">") and (variableValue > (item 2 of notePieces) as number) then | |
set condition to true | |
else if (theFunction is "<") and (variableValue < (item 2 of notePieces) as number) then | |
set condition to true | |
else | |
set theNotePart to item 2 of notePieces | |
try | |
set theNotePart to theNotePart as number | |
end try | |
if (((variableValue is item 2 of notePieces) and (theFunction is "==")) or ((variableValue is not item 2 of notePieces) and (theFunction is "!="))) then | |
set condition to true | |
end if | |
end if | |
on error | |
return | |
end try | |
if condition is false then | |
my clearNote(theTask, paraPointer) | |
return | |
end if | |
if theOperation is "delete" then | |
set note of theTask to "!!!Delete" | |
else if theOperation is "complete" then | |
set completed of theTask to true | |
end if | |
end tell | |
end tell | |
end conditionalCheck | |
on clearEmpties(theList) | |
set newList to {} | |
repeat with i from 1 to length of theList | |
if (item i of theList is not "") and (item i of theList is not " ") then | |
set the end of newList to item i of theList | |
end if | |
end repeat | |
return newList | |
end clearEmpties | |
on determineOperation(theNote) | |
if theNote contains "delete" then return "delete" | |
if theNote contains "complete" then return "complete" | |
if theNote contains "defer" then return "defer" | |
end determineOperation | |
on determineFunction(theNote) | |
if theNote contains "<=" then return "<=" | |
if theNote contains ">=" then return ">=" | |
if theNote contains "==" then return "==" | |
if theNote contains "!=" then return "!=" | |
if theNote contains "<" then return "<" | |
if theNote contains ">" then return ">" | |
end determineFunction | |
on clearNote(theTask, paraPointer) | |
tell application "OmniFocus" | |
tell default document | |
try | |
set theNote to every paragraph of the note of theTask | |
on error | |
return | |
end try | |
set my text item delimiters to return | |
if length of theNote is 1 then | |
set the note of theTask to "" | |
return | |
end if | |
if paraPointer is 1 then | |
set the note of theTask to (items 2 thru -1 of theNote) as string | |
else if paraPointer is (length of theNote) then | |
set the note of theTask to (items 1 thru -2 of theNote) as string | |
else | |
set the note of theTask to ((items 1 thru (paraPointer - 1)) & (items (paraPointer + 1) thru -1)) as text | |
end if | |
end tell | |
end tell | |
end clearNote | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment