Skip to content

Instantly share code, notes, and snippets.

@csreed
Last active December 21, 2020 03:46
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save csreed/3198e48d1227cdfa5dbc to your computer and use it in GitHub Desktop.
Save csreed/3198e48d1227cdfa5dbc to your computer and use it in GitHub Desktop.
Build a summary of tasks completed today
//
// Build a summary of OmniFocus tasks completed today.
//
// This is intended to be run as a TextExpander macro,
// but will work anywhere you can invoke a JS script.
//
// v 1.0.2 (full release history at bottom)
var NoProjectMarker = "No Project";
getTaskSummary();
function getTaskSummary() {
var doc = Application('OmniFocus').defaultDocument;
var thisMorning = startOfDay();
var tasks = doc.flattenedTasks.whose({completionDate: {'>=':thisMorning} })();
if (tasks.length == 0) {
return "No tasks completed. Do something without writing it down?";
}
// Group tasks by project, then extract progressed projects (projects with
// completed tasks) from allProjects. This gives us the list of projects
// in the same order as OmniFocus’s UI.
var groupedTasks = groupArrayByKey(tasks, function(v) {
var proj = v.containingProject();
if (proj) {
return proj.id();
}
return NoProjectMarker;
});
var allProjects = doc.flattenedProjects();
var progressedProjects = allProjects.filter(function(p) {
return p.id() in groupedTasks;
});
// Build the summary
var summary = progressedProjects.reduce(function(s,p){
return s + summaryForProject(p);
}, "");
var tasksWithNoProject = groupedTasks[NoProjectMarker];
if (tasksWithNoProject) {
summary += summaryForTasksWithTitle(tasksWithNoProject, "No Project\n");
}
return summary;
// This needs to be in this scope because it captures groupedTasks
function summaryForProject(p) {
var projectID = p.id();
var tasks = groupedTasks[projectID].filter(function(t) {
return projectID != t.id(); // Don't include the project itself
});
return summaryForTasksWithTitle(tasks, projectSummaryLine(p));
}
function summaryForTasksWithTitle(tasks, title) {
return title + tasks.reduce(summaryForTasks,"") + "\n";
}
}
// Reducing function to summarize a list of tasks
function summaryForTasks(s,t) {
return s + lineForTask(t);
}
// Create a summary line for a project
function projectSummaryLine(project) {
var tokens = [];
tokens.push(project.name());
var remainingStatus = remainingStatusForProject(project);
if (remainingStatus != "") {
tokens.push("(" + remainingStatus + ")");
}
return tokens.join(" ") + "\n";
}
// This is appended to the end of a project line
function remainingStatusForProject(project) {
if (!project.completedByChildren()) {
return "";
}
var remainingCount = project.numberOfTasks() - project.numberOfCompletedTasks();
if (remainingCount == 0) {
return "complete";
}
return remainingCount + " remaining";
}
function lineForTask(task) {
return " ✓ " + task.name() + "\n";
}
// Group an array of items by the key returned by this function
function groupArrayByKey(array,keyForValue) {
var dict = {};
for (var i = 0; i < array.length; i++) {
var value = array[i];
var key = keyForValue(value);
if (!(key in dict)) {
dict[key] = [];
}
dict[key].push(value);
}
return dict;
}
function startOfDay() {
// The day started at midnight this morning
var d = new Date();
d.setHours(0);
d.setMinutes(0);
d.setSeconds(0);
return d;
}
// Release History
// 1.0 Initial release
// 1.0.1 Fix for tasks without a project
// 1.0.2 Relaxed the type checking (!== is too strict)
@torgerson
Copy link

Fantastic! Thanks for this.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment