Flat filter results? (flat list of tasks)


#1

Is it possible to create a filter which only shows tasks that have a specific tag, without the hierarchy?

Or would I have to write a script to do this?


#2

The API’s outline.evaluateItemPath method returns a flat array, so you just need to:

  • feed a chosen itemPath String into the TaskPaper 3 JS Context from JXA
  • map over the returned array to make the kind of report that you want.

Rough sketch here, for example, listing lines with @flag tags

(() => {
    'use strict';

    // TASKPAPER 3 CONTEXT ---------------------------------------------------
    const tpJSContext = (editor, options) => {

        // pathReport :: itemPath String -> String
        const pathReport = path =>
            editor.outline.evaluateItemPath(path)
            .map(x => x.bodyString) // or .bodyContentString
            .join('\n');

        return pathReport(options.path);
    };


    // JXA CONTEXT------------------------------------------------------------

    const main = () => {
        const
            ds = Application('TaskPaper')
            .documents,
            lrResult = bindLR(
                ds.length > 0 ? (
                    Right(ds.at(0))
                ) : Left('No documents open'),
                doc => Right(doc.evaluate({
                    script: tpJSContext.toString(),
                    withOptions: {
                        path: '//@flag'
                    }
                }))
            );
        return lrResult.Right || lrResult.Left;
    };

    // GENERIC FUNCTIONS -----------------------------------------------------

    // Left :: a -> Either a b
    const Left = x => ({
        type: 'Either',
        Left: x
    });

    // Right :: b -> Either a b
    const Right = x => ({
        type: 'Either',
        Right: x
    });

    // bindLR (>>=) :: Either a -> (a -> Either b) -> Either b
    const bindLR = (m, mf) =>
        m.Right !== undefined ? (
            mf(m.Right)
        ) : m;

    // MAIN ---
    return main();
})();

#3

Thanks! Not used to ES6 syntax, so it took a while to decipher your example :wink:

… but you are basically joining items on the path with '\n' and TaskPaper inserts whatever the evaluated script returns right?

I looked at some examples here on the forum and put together this which works form me:

function TPContext(editor, options) {
	var outline = editor.outline;
	var root = outline.root;

	// enable undo for script changes
	outline.groupUndoAndChanges(function() {
	
		// empty or create project
		var project = outline.evaluateItemPath("/" + options.project + ":")[0];
		if (!project) {
			// create project if not found
			project = outline.createItem(options.project);
			root.insertChildrenBefore(project, root.firstChild);
		} else {
			// empty project if it exists
			var projectItems = outline.evaluateItemPath("/" + options.project + ":/*");
			Item.getCommonAncestors(projectItems).forEach(function(item) {
				item.removeFromParent();
			});
		}
		
		// get tasks tagged with tag provided in options
		var taggedTasks = outline.evaluateItemPath("@type=task and " + options.tag);
		
		// create and append new tasks under project
		taggedTasks.forEach(function(taggedTask) {
			project.appendChildren(outline.createItem(taggedTask.bodyString));
		});
	}); 
}

var TaskPaper = Application('TaskPaper');
TaskPaper.includeStandardAdditions = true;

// edit options to change tag and project name
TaskPaper.documents[0].evaluate({
	script: TPContext.toString(),
	withOptions: { 'tag': '@today',
	               'project': 'Today' }
});

#4

Looks good !


#5

PS on the ES5 ⇄ ES6 issue, here, FWIW, is an ES5 version of the first sketch.

(On-line conversion by the Babel REPL)

(function() {
    'use strict';

    // TASKPAPER 3 CONTEXT ---------------------------------------------------

    function tpJSContext(editor, options) {

        // pathReport :: itemPath String -> String
        function pathReport(path) {
            return editor.outline.evaluateItemPath(path)
                .map(function(x) {
                    return x.bodyString;
                }) // or .bodyContentString
                .join('\n');
        };

        return pathReport(options.path);
    };

    // JXA CONTEXT------------------------------------------------------------

    function main() {
        var ds = Application('TaskPaper').documents,
            lrResult = bindLR(ds.length > 0 ? (
                    Right(ds.at(0))
                ) : Left('No documents open'),
                function(doc) {
                    return Right(doc.evaluate({
                        script: tpJSContext.toString(),
                        withOptions: {
                            path: '//@flag'
                        }
                    }));
                });
        return lrResult.Right || lrResult.Left;
    };

    // GENERIC FUNCTIONS -----------------------------------------------------

    // Left :: a -> Either a b
    function Left(x) {
        return {
            type: 'Either',
            Left: x
        };
    };

    // Right :: b -> Either a b
    function Right(x) {
        return {
            type: 'Either',
            Right: x
        };
    };

    // bindLR (>>=) :: Either a -> (a -> Either b) -> Either b
    function bindLR(m, mf) {
        return m.Right !== undefined ? mf(m.Right) : m;
    };

    // MAIN ---
    return main();
})();

#6

Thanks, the arrow function stuff is just syntax, it was the Left/Right/bindLR stuff mostly which I had to figure out. Looks very functional. Is it from Haskell? What is the name of the pattern?

Its basically setting up one class for “stuff to return if things are good” (right)
and stuff to return if things are bad (left)? Looks like a very mathematical try/catch formalism.

why not just do

(function () {
    var ds = Application('TaskPaper').documents;

    function tpJSContext(editor, options) {
        var path = options.path;
        return editor.outline.evaluateItemPath(path).map(
            function(x) { return x.bodyString; } ).join('\n'); };
    };

    if ( ds.length > 0) {
        return doc.evaluate({ script     : tpJSContext.toString(),
                              withOptions: { path: '//@flag' } } );
    } else {
        return "No documents open";
    }
});

… which is what your code does? Why all the overhead? What am I missing? I’m just curious about the pattern, no criticism intended :slight_smile:


#7

Good questions !

using bind (>>=) functions is a building style which is useful enough to become habit-forming with larger-scale scripts, and, once habitual, can just become the simplest and most cut-and-paste way to write anything fast.

(with several nested binds it proves very useful to have two channels (blnSuccess, and some computed value) because with scope for evaluation failures at several stages e.g.

  • is there a file at that path ?
  • can we parse it as JSON ?
  • do we find the value we need ?
  • does the computation on that value succeed ?
  • etc etc.

delegating composition to a chain of generic bind functions save us from having to break (or plumb) the pipeline, which just automatically passes any failure messages (or successful results) right through to the end.

As you say, no particular need for that approach, especially at smaller scales, but even there, just pasting a pre-baked bind function saves me from having to hand-write the conditionals yet again.

(pattern name ? well, I guess you could describe use of bindLR as ‘the Either monad’, but its just a well-tested way of pipelining functions which return usefully wrapped output values, without the next function in the chain choking on unexpected wrapping)

I write code mainly for my own work, and the fastest way to do that is by pasting and composing pre-existing generic functions as if they were Lego bricks. As you noticed, I do find that the functions in the Haskell prelude and base modules provide a very useful and well-tested model. Protects me from endless wheel-reinvention : - )

The set of generic functions that I personally happen to use in JS is here:

with a parallel set in Applescript,

and side by side Markdown files in both repositories.

Keyboard Maestro macros for pasting from these:



#8

Thanks for the answers! I find that JavaScript can be very idiosyncratic, almost as if the language itself encourages people to come up with all sorts of (in my experience) overly verbose patterns.

I did skim through some of the ES6 documentation and it seems like the language is getting more consistent, and perhaps even more readable.

Thanks for the JXA links! :+1:

From time to time I have been trying to find good JXA resources. Have not been able to find almost any documentation on JXA on Apple’s developer pages.


#9

Good luck !

Incidentally, thinking about your question, I should probably have explained that when I need a quick TaskPaper script, I type tpe, which Typinator expands to the code below, so I don’t even give active thought to using a bind in that seed kernel.

The value it has in my particular context is just that if want to build any further complexity around that seedling on the JXA side, it is already structured by default as a bind, to which I can just add further layers.

Anyway, have fun : - )

(() => {
    'use strict';

    // TASKPAPER 3 CONTEXT ---------------------------------------------------
    const tpJSContext = (editor, options) => {

        // ...
  
    };


    // JXA CONTEXT------------------------------------------------------------

    const main = () => {
        const
            ds = Application('TaskPaper')
            .documents,
            lrResult = bindLR(
                ds.length > 0 ? (
                    Right(ds.at(0))
                ) : Left('No documents open'),
                doc => Right(doc.evaluate({
                    script: tpJSContext.toString(),
                    withOptions: {
                        path: '//@flag'
                    }
                }))
            );
        return lrResult.Right || lrResult.Left;
    };

    // GENERIC FUNCTIONS -----------------------------------------------------

    // Left :: a -> Either a b
    const Left = x => ({
        type: 'Either',
        Left: x
    });

    // Right :: b -> Either a b
    const Right = x => ({
        type: 'Either',
        Right: x
    });

    // bindLR (>>=) :: Either a -> (a -> Either b) -> Either b
    const bindLR = (m, mf) =>
        m.Right !== undefined ? (
            mf(m.Right)
        ) : m;

    // MAIN ---
    return main();
})();