Scripts to set priority, and sort a project by priority

My goal was to have projects in TaskPaper that had a priority tag and then to have a script that sorted the list by the priority.
Requirements:

  • Set the priority tag based on the Pareto Principle found here
    ** The @priority tag is composed of two values, an effort, and a result.
    ** Effort is a number, 1-3, indicating the effort required to accomplish a task (higher = more effort)
    ** Result is a number, 1-3, indicating the resulting benefit (higher = more benefit)
    ** Priority is then set as @priority(effort/result) so that priority value = higher priority task (this is intuitive to me)
  • Sort all the tasks in each project according to priority

Solution:
I wrote two scripts to do this.

  1. The first one pops up a small dialog asking for a number for effort, and the second one for result. The script then inserts a @priority(effort/result) tag. I then attach this script to a hotkey in Alfred 3.
  2. The second script sorts each project (including nested projects) by priority.

These were my first scripts so they are likely poorly written. But they are well documented and perfect for newbie consumption since I had no clue what I was doing and so make them as easy to read and digest as possible. Please enjoy. And if anyone has improvements PLEASE do share!!

As a sidenote, I’ve written a couple of Alfred 3 workflows to quickly add tasks to the “inbox” of my taskpaper files and could share if anyone is interested.

Script to set priority:

(function () {
// JavaScript for the Automation context
var min = 1;
var max = 4;
var default_val = 2;

// get the application TaskPaper
TP = Application("TaskPaper");
// get the standard additions which allow dialogs and other interactions with the system
// if you don't want TP to be the focused app, use Application.currentApplication() and set
// the standard additions to true in that app.
TP.includeStandardAdditions = true;
// note that JXA syntax is just weird, requiring the first parameter like normal, and
// subsequent parameters to be in curly braces with the name of the parameter a colon, then the variable, then a comma. Seems silly to me.
// get the effort required
var effort = TP.displayDialog('Set Effort...('+min+'-'+max+')',{
	withTitle: 'Set Effort for Task',
	defaultAnswer: default_val,
	buttons: ['OK','Cancel'],
	defaultButton: 'OK',
	cancelButton: 'Cancel'
	});
// convert to a number
effort = Number(effort.textReturned);
// check to see if input was valid
if(effort < min || effort > max) {
	return;
}

// get the result
var result = TP.displayDialog('Set Result...('+min+'-'+max+')',{
	withTitle: 'Set Result for Task',
	defaultAnswer: default_val,
	buttons: ['OK','Cancel'],
	defaultButton: 'OK',
	cancelButton: 'Cancel'
	});
// convert to a number
result = Number(result.textReturned);
if(result < min || result > max) {
	return;
}
// calculate the priority
var priority = (effort/result).toFixed(1);


function TaskPaperContextScript(editor, options) {

	// get the priority
	var priority = options;

	// sets the attribute
	function setAttribute(editor, items, name, value) {
	    var outline = editor.outline;
    	var selection = editor.selection;
	    outline.groupUndoAndChanges(function() {
    		items.forEach(function (each) {
		        each.setAttribute(name, value);
			});
		});
		editor.moveSelectionToItems(selection);
	}
	// pass to setAttribute the editor, the selectedItems
	setAttribute(editor, editor.selection.selectedItems, 'data-priority', priority);
}

// call the JavaScript
Application("TaskPaper").documents[0].evaluate({
  script: TaskPaperContextScript.toString(),
  withOptions: priority
})
})();

Sorting Script:

function TaskPaperContextScript(editor, options) {
	var outline = editor.outline;
	// group all the changes together into a single change and make it a single "undo" action
	outline.groupUndoAndChanges(function () {
		// all the projects
		var projects_array = outline.evaluateItemPath('@type=project except archive:');
		// for each project get the tasks with priority
		projects_array.forEach(function(each) {
			var has_children = each.hasChildren;
			if(!has_children) {
				return;
			}
			var children = each.children; 

			// handle the case of only one item in a project
			if(children.length < 2) {
				return;
			}

			children.sort(function (a, b) {
				// a and b are elements of the array
				// get the priority value of task a, "getAttribute" takes the attribute name,
				// and whether or not the value should be converted to something else (in
				// this case a number)
				//debugger;
				var a_priority = a.getAttribute('data-priority', Number);
				var b_priority = b.getAttribute('data-priority', Number);
				//var a_has_done = a.hasAttribute('data-done');
				//var b_has_done = b.hasAttribute('data-done');

				// need to return negative, zero, or positive
				// negative indicates a < b
				// zero indicates a = b
				// positive indicates a > b
				
				// Here null means the item didn't have a priority tag
				// In this case b < a
				if(a_priority == null && b_priority != null) {
					return 1 ;
				} // In this case a < b
				else if (a_priority != null && b_priority == null) {
					return -1;
				} // In thise case a = b
				else if (a_priority == null && b_priority == null) {
					return 0;
				} // In thise return a relative to b
				else {
					return a_priority - b_priority;
				}
			});	// end sort	
			each.removeChildren(each.children);
			each.appendChildren(children);
		}); // end projects_array.forEach 
	}); // end outline.groupUndoAndChanges
} // end TaskPaperContextScript

var string = Application("TaskPaper").documents[0].evaluate({
  script: TaskPaperContextScript.toString()
});

6 Likes

I am very impressed with the progress you managed in one single day. Is there any chance that you can talk about how did you arrived at this implementation? I am just interested in the actual process. I have been noticing that some people seem to be better at learning, but I would like to know why.

Hey Victor-
It probably shouldn’t be that impressive. I’ve loads of education, and am a professor in a Computer Science dept…although, Software Engineering is not my area of expertise. But I definitely know how to learn.

Anyway, I’ve worked as a software engineer before so coding isn’t new to me. I thought I knew JavaScript a bit, but I was definitely wrong. My two main problems were:

  1. I’m not used to JavaScript style functional programming (most of my work is in embedded systems so I write a lot of serialized C code).
  2. I think the documentation for the API for TaskPaper is a little bit tough to digest for someone not intimately familiar with JavaScript. A gentle intro to the API (especially for someone unfamiliar with JavaScript) would have probably cut about 3 hours off my efforts.

My method for overcoming these problems were:

  1. I read through a lot of examples
  2. I always try to break my code up to make it “self-documenting” and very readable including notes to my future self (or other interested readers). I learned this by going through many code reviews when I worked on a large distributed control system. This means I can test small pieces of the code.

Anyway, thanks for everyone’s help.

3 Likes

I look forward to trying these scripts. I like the principle of quickly allocating priority to tasks that includes the value to completing each task vs the time/effort required.

The Alfred Inbox workflows sound interesting too.

I don’t know if you noticed, but in your priority script, you are checking for effort twice (instead of checking for effort once, and then for result). I am looking forward to implement this on some of my documents.

@complexpoint, Is there any way to run the sorting script on all my task paper documents and create a new file with the results?

@complexpoint, Is there any way to run the sorting script on all my task paper documents and create a new file with the results?

That should be possible with the open source API which Jesse has released. I’m a little busy for the next few weeks, but I’m looking forward to sitting down with that properly in the fall

1 Like

Oops, thanks Victor. You’re absolutely right. Somehow that slipped by me. I’ll fix that in what I posted.

Hi all-
Just in case anyone decide to use this script, I’ve updated the sorting script in a rather significant way. It turns out I discovered a bug.

In the previous version I was using reduceRight() on the sorted children array to insert the new children into the list. This broke in the rare, but important case when the last element in the sorted array was the first child of the parent element. It amounted to trying to insert something before itself (a difficult proposition for anything).

So I modified it to remove all the existing children of the item, then append all the newly sorted children. This is appears to be much more robust, if a bit extreme. And fortunately, it doesn’t seem to break…yet.

Also note that you can assign priorities to projects and they will also get sorted amongst the other tasks and projects as well.

Enjoy.

2 Likes

@jmb275 And if anyone has improvements PLEASE do share!!

Thanks for sharing!

I was looking for a way to push @done items at the bottom of their projects, group them by “tasks” and “sub-projects” and sort them in ascending order of @done(date).

It’s not an improvement of your script but I was inspired by it… You can take a look at it here if you or anyone else is interested.

Cheers!

Am I to understand that, from these nice but complicated scripts that; 1) it is possible to sort tags by priority in TaskPaper and that, 2) TaskPaper will not sort search results in it’s natural state?

I do not see a way to put in all this script material in a TaskPaper saved search, therefore this would involve saving the script in am external script editor and somehow pointing the script at my TaskPaper document? Seems a little intimidating if that is true. Nice, but a sidetrack issue for me and how long it takes me to learn something.

I do love a good script and I will take the time to implement one - provided I understand how to both implement it and find it easily when I next need it but cannot remember where I put it. One thing I very much appreciate in TaskPaper is it’s relative simplicity.

You found this! Honestly, for most of the people, learning a scripting language may be too much; but if you get started using some samples and then ask questions, you can get a lot of help.

I know it has been a while, but is this a possibility? I am interested in seeing a script that creates another file using the results. I have a nice series of scripts (bash, ruby, and LaTex) that take some results, convert that to Multimarkdown → Latex → PDF - > print a determined amount of copies. It sounds complicated, but it is very seamless and produces really nice outputs according to my template. Other than I have to run a search, then copy the results to another file, and then run the script. It has saved me a lot of time (I make about 15 reports a week using this).

Shameless plug :relaxed:: I know, Emacs is not for everyone, but this is actually possible with my TaskPaper mode plugin for Emacs:

Tell me the spec, is it:

[FilePath] -> TP3 Document

(A list of file paths (of TaskPaper documents) -> a new document open in TaskPaper)

or

[TP3 Document] -> TP3 Document

(all documents open in TP3 -> a new TP3 document)

And how would you need to specify the sort criteria ?

entering a list of 1-3 (String, Bool) pairs (SortKey|TagName + isAscending) ?

or hardwiring a search spec at the head of a script ?

Anything else ?

PS if we are essentially talking about sorting a merged TP outline, what is the pattern of merging a pair of outlines ?

In particular, if we have a file with various projects, each with some degree of nesting beneath them, how do we decide what subset of items to extract, merge and sort ?

  • All top-level items, with descendants, of projects which share names ?
  • Or the level of sorting is all top-level projects ?
  • Or the top-level children of all projects, leaving the projects themselves in unchanged sequence ?
  • Something else ?

I am thinking of something a little bit more simple. I will let you know who I do it, and maybe that will clarify things.

I have this document that I use to keep track of things that people need to do or keep in mind and then I pass the list every week at one of our meetings.

1.- This is the query I do in my source document.

(@type/..* union task///*) except (( //@private/..* union @private///*) union (archive///* union @done))

2.- Then I copy the results into another document that I creatively call "toPrint.taskpaper"
3.- I run a ruby/bash script that does the following,

  • Using the Ruby library that Matt Gemmel contributed, I convert the taskpaper document into a temporary multimarkdown file. This could be done in something else other than Ruby, since my only requirement is to change all the projects into a level one header (to just #) regardless of the hierarchy of the project; and then clean up the taskpaper file of all its tags.
  • Using the MMD 6.0 LaTex Config ability to create templates, the script changes that mdd file into a nice LaTex file that then is processed into a PDF file using XeLaTex.
  • Using bash it cleans up the resulting log and aux files created by XeLaTex, renames the PDF according to the date, and moves the file to a Document repository in a NAS. Since this is something that happens every week, one in our organization can search all the PDF’s by folders. The folders are organized by something like handOut->YEAR->MONTH->name-based-on-date.
  • Lastly, using bash and the lpr command, the script asks the one who started the script how many copies of the current PDF file are needed and send those to print to a networked printer.

What I would like to do is see is if it would be possible to simplify things even more by having a script that does the query and creates the resulting taskPaper file (overwriting the old one) that is used by my other scripts. That would limited at least one of the steps that take the most time right now (step one and two).

Since I do this at least once a week, that amount of time adds up :slight_smile:; but I am also thinking to delegate this to someone who is a computer newb. Now, if you need to see some of my source files, so that everything is accomplished in JavaScript/Bash instead of Ruby/Bash I would gladly provide them. Like I said again, they are just simple modifications to the library Matt contributed.

@type/..*

Is that a safely sortable set ? I may well be misunderstanding, but that looks at first sight as if it might create an array in which sub-projects are listed as peers of their parent projects …

More generally, here :

you seemed to have in mind gathering and sorting items from several files, but if I am reading the newer description above correctly, you are working with a single source file, and the issue is more to do with creating a new file, and overwriting an old one ?

My caution about the search you are using is that if your data ever contained any subprojects, it looks at first sight as if there there might be some loss of structure (flattening + duplication) in the report, which makes overwriting feel a little vertiginous …

( PS the earlier goal - generating one report from several files, could, I think, be achieved with Jesse’s standalone Birch-outline API: birch-outline - npm, which I have had a chance to use since those earlier posts, and which does work very well).

Because of how the search queries work, if there is a parent project that had some tasks, even if those tasks are eliminated by the query, the parent project shows up as a result. I don’t remember how I figured it out, but using Paths I can clean up the results. That is why. I think I wrote a post about it somewhere.

Yeah. I formatted the information and pdf template in order to make that extra information irrelevant. I do like to keep that scheme in my original file because of organizational reasons.