Here is a generic recursion scheme (a function named foldlTP) for:
- starting with some initial value (a number, string, dictionary etc)
- walking systematically over the whole TaskPaper outline, while
- accumulating incremental updates to the initial value.
This simplest examples might be:
- Counting how many childless leaves an outline contains,
- finding the maximum indent level in an outline, or just
- counting the total number of items in an outline,
- or counting the number of items that have a particular tag.
foldlTP may look familiar if you have already acquainted yourself with the standard JavaScript Array methods Array.reduce and Array.reduceRight.
If you haven’t, I recommend them – they are wonderfully powerful and convenient.
Array.reduce walks from left to right through a flat JavaScript array, accumulating changes to an initial value.
foldlTP walks top-down left-left through the whole tree structure of a TaskPaper outline, accumulating changes to an initial value.
From the TP3 outline which I am looking at now, I could obtain the result:
{
"leafCount": 34,
"maxDepth": 5,
"nodeCount": 54,
"doneCount": 6
}
using this generic recursion-scheme function four times,
// foldlTP :: (a -> TPItem -> a) -> a -> TPItem -> a
const foldlTP = (f, acc, item) => {
const go = (a, x) =>
x.hasChildren ? x.children.reduce(
go,
f(a, x)
) : f(a, x);
return go(acc, item);
};
by writing something like the following, and testing it in Script Editor etc
(See Using Scripts · TaskPaper User's Guide )
(() => {
'use strict';
// TASKPAPER 3 CONTEXT ---------------------------------------------------
const tpJSContext = (editor, options) => {
// showJSON :: a -> String
const showJSON = x => JSON.stringify(x, null, 2);
// foldlTP :: (a -> TPItem -> a) -> a -> TPItem -> a
const foldlTP = (f, acc, item) => {
const go = (a, x) =>
x.hasChildren ? x.children.reduce(
go,
f(a, x)
) : f(a, x);
return go(acc, item);
};
// max :: Ord a => a -> a -> a
const max = (a, b) => b > a ? b : a;
// MAIN --------------------------------------------------------------
// root :: TP Item
const root = editor.outline.root;
// measure :: (Int -> Int -> Int) -> Int
const measure = f => foldlTP(f, 0, root);
return showJSON({
// (+1) if the item is childless
leafCount: measure((a, x) => a + (x.hasChildren ? 0 : 1)),
// Greater of (deepest so far) and (this item's depth)
maxDepth: measure((a, x) => max(a, x.depth)),
// (+1) for any item
nodeCount: measure(a => a + 1),
// (+1) for any @done item
doneCount: measure(
(a, x) => a + (x.hasAttribute('data-done') ? 1 : 0)
)
});
};
// JXA CONTEXT------------------------------------------------------------
// 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;
const
ds = Application('TaskPaper')
.documents,
lrResult = bindLR(
ds.length > 0 ? Right(ds.at(0)) : Left('No documents open'),
d => Right(d.evaluate({
script: tpJSContext.toString(),
withOptions: {}
}))
);
return lrResult.Right || lrResult.Left;
})();