Last active
February 3, 2019 10:50
-
-
Save doug4j/6e98e6b2435bf2d45ae061100addca2b to your computer and use it in GitHub Desktop.
This file contains 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
// jshint esversion:6 | |
(function() { | |
var start = new Date().getTime(); | |
var app = Application('OmniFocus'); //This assumes that OmniFocus is installed on MacOS | |
app.includeStandardAdditions = true; | |
var doc = app.defaultDocument; | |
var content = doc.documentWindows.at(0).content; | |
var selectedTree = content.selectedTrees(); | |
var selectedLen = selectedTree.length; | |
var totalTasks = 0; | |
var elapsedMsg = ""; | |
var estimatedDuration = ""; | |
var tasks = []; | |
for (var i=0;i < selectedLen; i++) { | |
try{ | |
var task = selectedTree[i]; | |
if (isLeafTask(task)) { | |
tasks.push(task); | |
totalTasks = totalTasks + 1; | |
} | |
} catch(e) { | |
if (e.message === "User canceled.") { | |
return; //get out of the loop (end the program) | |
} else { | |
app.displayDialog("[Exception] Content Selected index [" + i + "] is not a task: " + e); | |
} | |
} | |
} | |
if (tasks.length > 1) { | |
elapsedMsg = "Elapsed time thusfar: " + elapsedTimeMsg(start); | |
estimatedDuration = estimateElapsedTimeMsg(tasks.length); | |
app.displayDialog("It is estimated that verification will take " + estimatedDuration + ". Press OK to continue or cancel to stop. " + elapsedMsg); | |
} | |
var validationErrors = []; | |
tasks.forEach(task => { | |
var expected = verifyTaskStructure(task); | |
if (expected.isError) { | |
validationErrors.push(expected); | |
} | |
}); | |
function isLeafTask(item) { | |
try { | |
//app.displayDialog(item.value().name()) | |
if(item.value().inInbox()) { | |
return true; | |
} | |
if (item.value().containingProject().id() === item.value().id()) { | |
//This is a project, tag, or some other top-level container | |
return false; | |
} | |
//app.displayDialog("could be a leaf task:" + item.value().name()) | |
//app.displayDialog("numberOfTasks:" + item.value().numberOfTasks()) | |
//item.flattenTasks().length === 0 | |
return item.value().numberOfTasks() == 0; | |
} catch (e) { | |
return false; | |
} | |
} | |
function newValidatorMandaotryEstimatedMinutes() { | |
var answer = { | |
isError: true, | |
validatorType: "MandatoryEstimatedMinutes", | |
toStringDetails: function() { | |
return "\n - validatorType [" + this.validatorType + "]"; | |
}, | |
}; | |
return answer; | |
} | |
function newValidatorMandatoryOneTagOnly(rootTag) { | |
var answer = { | |
usageCount: 0, | |
isError: true, | |
usedTags: [], | |
rootTag: rootTag, | |
validatorType: "MandatoryOneTagOnly", | |
toStringDetails: function() { | |
return "\n - validatorType [" + this.validatorType + | |
"]\n usageCount [" + this.usageCount + | |
"]\n rootTag [" + this.rootTag + "]"; | |
}, | |
}; | |
return answer; | |
} | |
function newValidatorAtLeastOneTag(rootTag) { | |
var answer = { | |
usageCount: 0, | |
isError: true, | |
usedTags: [], | |
rootTag: rootTag, | |
validatorType: "AtLeastOneTag", | |
toStringDetails: function() { | |
return "\n - validatorType [" + this.validatorType + | |
"]\n usageCount [" + this.usageCount + | |
"]\n rootTag [" + this.rootTag + "]"; | |
}, | |
}; | |
return answer; | |
} | |
function isNotTaskSystemInternal(item) { | |
var tags = item.value().tags(); | |
for(var i=0;i < tags.length;i++){ | |
var tag = tags[i]; | |
if (tag.name() === "Sys Internal") { | |
return false; | |
} | |
} | |
return true; | |
} | |
function getWhen(item) { | |
var tags = item.value().tags(); | |
for(var i=0;i < tags.length;i++){ | |
var tag = tags[i]; | |
var rootTag = getRootTag(tag); | |
if (rootTag.name() === "Whens") { | |
return tag; | |
} | |
} | |
return null; | |
} | |
function verifyTaskMandaotryEstimatedMinutes(task, compositValidator) { | |
var when = getWhen(task); | |
if (deepDetailWhenEligible(when)) { | |
var estimatedMinutes = task.value().estimatedMinutes(); | |
if (estimatedMinutes === null) { | |
compositValidator.isError = true; | |
compositValidator.errorValidators.push(newValidatorMandaotryEstimatedMinutes()); | |
} else if (estimatedMinutes <= 0) { | |
compositValidator.isError = true; | |
compositValidator.errorValidators.push(newValidatorMandaotryEstimatedMinutes()); | |
} | |
} | |
} | |
function deepDetailWhenEligible(when) { | |
if (when === null) { | |
return false; | |
} | |
if ( (when.name() === "Now") || (when.name() === "Next") || (when.name() === "Weekend") || | |
(when.name() === "Daily Start") || (when.name() === "Daily End") || (when.name() === "Weekly") ) { | |
return true; | |
} | |
return false; | |
} | |
function verifyTaskMandatoryOneTagOnlyItems(task, compositValidator) { | |
var tags = task.value().tags(); | |
var expectedMandatoryOneTagOnlyItems = { | |
"Whens": newValidatorMandatoryOneTagOnly("Whens"), | |
"Organizations": newValidatorMandatoryOneTagOnly("Organizations"), | |
}; | |
var when = getWhen(task); | |
if (deepDetailWhenEligible(when)) { | |
expectedMandatoryOneTagOnlyItems.Energies = newValidatorMandatoryOneTagOnly("Energies"); | |
expectedMandatoryOneTagOnlyItems.Sources = newValidatorMandatoryOneTagOnly("Sources"); | |
expectedMandatoryOneTagOnlyItems["Stale Dates (End of)"] = newValidatorAtLeastOneTag("Stale Dates (End of)"); | |
} | |
for(var i=0;i < tags.length;i++){ | |
var tag = tags[i]; | |
var rootTag = getRootTag(tag); | |
for(let key of Object.keys(expectedMandatoryOneTagOnlyItems)) { | |
if (rootTag.name() == key) { | |
var thisTagStatistics = expectedMandatoryOneTagOnlyItems[key]; | |
thisTagStatistics.usageCount = thisTagStatistics.usageCount + 1; | |
if (thisTagStatistics.usageCount > 1) { | |
thisTagStatistics.isError = true; | |
} | |
if (thisTagStatistics.usageCount == 1) { | |
thisTagStatistics.isError = false; | |
} | |
} | |
} | |
} | |
for(let key of Object.keys(expectedMandatoryOneTagOnlyItems)) { | |
var expectedMandatoryOneTagOnly = expectedMandatoryOneTagOnlyItems[key]; | |
if(expectedMandatoryOneTagOnly.isError) { | |
compositValidator.isError = true; | |
compositValidator.errorValidators.push(expectedMandatoryOneTagOnly); | |
} | |
} | |
} | |
function verifyTaskAtLeastOneTagItems(task, compositValidator) { | |
var tags = task.value().tags(); | |
var expectedAtLeastOneTagItems = {}; | |
var when = getWhen(task); | |
if (deepDetailWhenEligible(when)) { | |
expectedAtLeastOneTagItems.Sources = newValidatorMandatoryOneTagOnly("Sources"); | |
expectedAtLeastOneTagItems.Deliverables = newValidatorAtLeastOneTag("Deliverables"); | |
expectedAtLeastOneTagItems["Related Parties"] = newValidatorAtLeastOneTag("Related Parties"); | |
} | |
tags.forEach(tag => { | |
var rootTag = getRootTag(tag); | |
for(let key of Object.keys(expectedAtLeastOneTagItems)) { | |
if (rootTag.name() == key) { | |
var thisTagStatistics = expectedAtLeastOneTagItems[key]; | |
thisTagStatistics.usageCount = thisTagStatistics.usageCount + 1; | |
if (thisTagStatistics.usageCount >= 1) { | |
thisTagStatistics.isError = false; | |
} | |
} | |
} | |
}); | |
for (let key of Object.keys(expectedAtLeastOneTagItems)) { | |
let expectedOneTagOnly = expectedAtLeastOneTagItems[key]; | |
if (expectedOneTagOnly.isError) { | |
compositValidator.isError = true; | |
compositValidator.errorValidators.push(expectedOneTagOnly); | |
} | |
} | |
} | |
function verifyTaskStructure(task) { | |
var compositValidator = { | |
isError: false, | |
errorValidators: [], | |
name: task.name(), | |
id: task.id(), | |
objectToValidate: "Task", | |
}; | |
if (isNotTaskSystemInternal(task)) { | |
verifyTaskMandatoryOneTagOnlyItems(task, compositValidator); | |
verifyTaskAtLeastOneTagItems(task, compositValidator); | |
verifyTaskMandaotryEstimatedMinutes(task, compositValidator); | |
} | |
return compositValidator; | |
} | |
function getRootTag(tag){ | |
var parentTag = tag.container(); | |
if (parentTag.name().trim() === "OmniFocus") { | |
return tag; | |
} | |
return getRootTag(parentTag); | |
} | |
function estimateElapsedTimeMsg(count) { | |
var elapsedSecs = count * 1.6; | |
var elapsedMsg = ""; | |
if (elapsedSecs > 60) { | |
elapsedMsg = Math.floor(elapsedSecs / 60) + " min(s) " + (elapsedSecs % 60).toFixed(2) + " sec(s)"; | |
} else { | |
elapsedMsg = elapsedSecs.toFixed(2) + " sec(s)"; | |
} | |
return elapsedMsg; | |
} | |
function elapsedTimeMsg(startTime) { | |
var elapsedSecs = ((new Date().getTime() - start) / 1000); | |
var elapsedMsg = ""; | |
if (elapsedSecs > 60) { | |
elapsedMsg = Math.floor(elapsedSecs / 60) + " min(s) " + (elapsedSecs % 60).toFixed(2) + " sec(s)"; | |
} else { | |
elapsedMsg = elapsedSecs.toFixed(2) + " sec(s)"; | |
} | |
return elapsedMsg; | |
} | |
elapsedMsg = "Total Elapsed time: " + elapsedTimeMsg(start); | |
var result = ""; | |
if (validationErrors.length == 0) { | |
result = "OK"; | |
app.displayDialog("Overall Validation as [" + result + "] for " + | |
totalTasks + " task(s) of " + (selectedLen - totalTasks) + | |
" non-leaf item(s).\n" + "Estimated duration: " + estimatedDuration + "\n" + elapsedMsg); | |
} else { | |
result = "Error"; | |
var maxDisplayError = 3; | |
var currentErrorCount = 0; | |
var errSumary = ""; | |
for (let validationError of validationErrors) { | |
currentErrorCount = currentErrorCount + 1; | |
if (currentErrorCount > maxDisplayError) { | |
break; | |
} | |
if (errSumary != "") { | |
errSumary = errSumary + "\n"; | |
} | |
errSumary = errSumary + " #" + currentErrorCount + " Validation Error [" + validationError.name + "] of objectToValidate [" + validationError.objectToValidate + "]"; | |
for (let validator of validationError.errorValidators) { | |
errSumary = errSumary + validator.toStringDetails(); | |
} | |
} | |
if (validationErrors.length <= maxDisplayError) { | |
app.displayDialog("Overall Validation as [" + result + "] " + validationErrors.length + " all errors for " + totalTasks+ " task(s) of " + (selectedLen - totalTasks) +" non-leaf item(s)\n" + errSumary); | |
} else { | |
app.displayDialog("Overall Validation as [" + result + "] showing " + maxDisplayError + " of " + validationErrors.length + " errors for " + totalTasks + " task(s) of " + (selectedLen - totalTasks) + " non-leaf item(s)\n" + errSumary); | |
} | |
} | |
})(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment