Creating printable TaskPaper 3 reports

A draft example of a script which places a Due report (Markdown with some HTML color tags) in the clipboard, so that you can view/print it from a Marked 2 clipboard preview.

Sample output, (JavaScript for Automation source below).

(This report groups items by due date. They are sorted, within day headings, by any time component of the @due(dateTime) tag in that item. Tags other than @due are displayed in the text, which is preceded by the project title, as well as any specific time).

// DUE REPORT script for TaskPaper 3 ver 0.04

var strClip = (function () {
    'use strict';

    function fnTP3Context(editor, options) {

        // concatMap ::  [a] -> (a -> [b]) -> [b]
        function concatMap(xs, f) {
            return [].concat.apply([], xs.map(f));
        }

        function itemProject(item) {
            var p = item.parent;

            while (p) {
                if (p.getAttribute('data-type') === 'project') {
                    return p.bodyContentString;
                };
                p = p.parent;
            }
            return '';
        }

        function timePart(item, strTimeTag) {
            var strDate = item.getAttribute('data-' + strTimeTag);

            return strDate ? strDate.slice(10, 16)
                .trim() : '';
        }

        function otherTags(lstAttribNames, attribs, strGroup) {
            return concatMap(
                    lstAttribNames,
                    function (k) {
                        var strTag = k.indexOf(
                                'data-'
                            ) === 0 ? k.slice(5) :
                            '',
                            strValue = attribs[k];

                        // Non-grouping tag attributes included
                        return (
                            strTag && ['type', strGroup]
                            .indexOf(strTag) === -1
                        ) ? [
                '@' + strTag + (strValue ? '(' + strValue + ')' : '')
                 ] : [];
                    }
                )
                .join(' ');
        }

        var outline = editor.outline;


        // READING OF SORT SPEC
        var lstOrder = options.orderBy.split(/\s+/),
            strSortBy = lstOrder[0].trim(),
            blnZA = (lstOrder.length > 1) && (lstOrder[1].trim()
                .substr(0, 4)
                .toLowerCase() === 'desc'),
            strSortAttrib = 'data-' + strSortBy;


        // READING OF GROUPING SPEC
        var lstGroup = options.groupBy.split(/\./),
            strGroup = lstGroup[0],
            strGroupAttrib = 'data-' + strGroup,
            strGroupFn = lstGroup.length > 1 ? lstGroup[1] : undefined;

        
            // FILTERED BY ...
         var lstFiltered = outline.evaluateItemPath(options.filter),

            // GROUPED BY ...
            dctGroups = lstFiltered.reduce(
                function (groups, dctItem) {
                    var v = dctItem.getAttribute(strGroupAttrib),
                        vGroup = strGroupFn ? eval(
                            '"' + v.toString() + '".' + strGroupFn
                        ) : v,
                        dctGroup = groups[vGroup] || {};

                    dctGroup[dctItem.id] = dctItem;
                    groups[vGroup] = dctGroup;

                    return groups;
                }, {}
            ),

            // GROUPS ORDERED BY
            lstGroups = Object.keys(dctGroups)
            .sort(blnZA ?
                function (a, b) {
                    return a === b ? 0 : (a < b ? 1 : -1);
                } : function (a, b) {
                    return a === b ? 0 : (a > b ? 1 : -1);
                });

        return "### <font color='silver'>" + strGroup.charAt(0)
            .toUpperCase() + strGroup.slice(1) + "</font>\n\n" +
            lstGroups.map(function (k) {
                var dctGroup = dctGroups[k];

                return "#### <font color=gray>" + k.substr(0, 7) +
                    '</font>&nbsp;<font color=red>' + k.slice(8) +
                    '</font>\n' + Object.keys(dctGroup)
                    .map(function (id) {
                        var item = dctGroup[id];

                        var strTime = timePart(item, strGroup),
                            strProject = itemProject(item),
                            attribs = item.attributes,

                            strPrefixes = (strTime ?
                                '<font color=gray><b>' +
                                strTime +
                                '</font></b>\t' :
                                '') + (strProject ?
                                '<font color=gray>' +
                                strProject +
                                '</font>&nbsp;' : '');

                        return strPrefixes +
                            // body text
                            item.bodyContentString
                            // Any remaining (non-grouping) tags
                            + ' <font color=gray>' + otherTags(
                                item.attributeNames,
                                attribs,
                                strGroup
                            ) + '</font>';
                    })
                    .sort()
                    .join('\n');
            })
            .join('\n\n');
    }

    var ds = Application("TaskPaper")
        .documents,
        varResult = ds.length ? ds[0].evaluate({
            script: fnTP3Context.toString(),
            withOptions: {
                filter: '//@due',
                groupBy: 'due.substr(0, 10)',
                orderBy: 'due'
            }
        }) : false;

    return varResult;
})();


var a = Application.currentApplication(),
    sa = (a.includeStandardAdditions = true, a);

sa.setTheClipboardTo(strClip);

strClip
4 Likes

Hello,
I just started using TaskPaper (I have experience with FoldingText and running scripts for TP).
Running the script in TP3 results in the following:
“TypeError: undefined is not an object (evaluating ‘attribs.trailingMatch.length’)\n\n\nmap@[native code]\n\nmap@[native code]\nfnTP3Context\nevaluateScript@file:///Applications/TaskPaper.app/Contents/Resources/dist/birch.js:50204:16\n\n\tUse the Help > SDKRunner to debug”

FYI I am running the script as a JavaScript (not AppleScript).

Do you have any idea what could be going wrong?
Thank you,
Paul

Well caught – there has been a slight shift in the API between that Preview example and the release version.

I’ll take a look and update it this evening

UPDATE

I’ve made a couple of edits.

It’s still just an illustrative draft, but I think you may find that it is now compatible with the release version of TaskPaper 3.

Thank you; the error message is gone now.

I am not (yet) using Marked 2, or another Markdown editor/viewer. I used a simple editor to check the clipboard’s contents and recognized some of the actions with due dates, so it seems to be working.

As an alternative to using Marked 2, would it be possible to have the (adapted) script’s output appear in a new TaskPaper file (instead of in the clipboard)?

Thank you, Paul

It’s certainly possible to generate grouped reports like this in any text format. I’m not sure how one would best handle the date headings and time prefix in a specifically TaskPaper format.

Do you have an example of what the output that you envisage might look like ?

( http://marked2app.com/ is an excellent investment incidentally if you are using plain text workflows )

Yes I have an example, see the link below, referring to a similar solution for FoldingText (thanks to your help).

(But yes, I guess I should invest in Marked 2…)

Here is the link:
http://support.hogbaysoftware.com/t/how-to-adapt-script-to-include-project-label-in-list-of-actions-sorted-by-due-date/1471/5

OK, well the first thing would just be to pull out the HTML tags, and perhaps the MD hashes, see what you get, and add back in anything else that you want

Here’s a start - I leave the final details as an exercise for the reader :slight_smile:

var strClip = (function () {
    'use strict';

    function fnTP3Context(editor, options) {

        // concatMap ::  [a] -> (a -> [b]) -> [b]
        function concatMap(xs, f) {
            return [].concat.apply([], xs.map(f));
        }

        function itemProject(item) {
            var p = item.parent;

            while (p) {
                if (p.getAttribute('data-type') === 'project') {
                    return p.bodyDisplayString;
                };
                p = p.parent;
            }
            return '';
        }

        function timePart(item, strTimeTag) {
            var strDate = item.getAttribute('data-' + strTimeTag);

            return strDate ? strDate.slice(10, 16)
                .trim() : '';
        }

        function otherTags(lstAttribNames, attribs, strGroup) {
            return concatMap(
                    lstAttribNames,
                    function (k) {
                        var strTag = k.indexOf(
                                'data-'
                            ) === 0 ? k.slice(5) :
                            '',
                            strValue = attribs[k];

                        // Non-grouping tag attributes included
                        return (
                            strTag && ['type', strGroup]
                            .indexOf(strTag) === -1
                        ) ? [
                '@' + strTag + (strValue ? '(' + strValue + ')' : '')
                 ] : [];
                    }
                )
                .join(' ');
        }

        var outline = editor.outline;


        // READING OF SORT SPEC
        var lstOrder = options.orderBy.split(/\s+/),
            strSortBy = lstOrder[0].trim(),
            blnZA = (lstOrder.length > 1) && (lstOrder[1].trim()
                .substr(0, 4)
                .toLowerCase() === 'desc'),
            strSortAttrib = 'data-' + strSortBy;


        // READING OF GROUPING SPEC
        var lstGroup = options.groupBy.split(/\./),
            strGroup = lstGroup[0],
            strGroupAttrib = 'data-' + strGroup,
            strGroupFn = lstGroup.length > 1 ? lstGroup[1] : undefined;

        
            // FILTERED BY ...
         var lstFiltered = outline.evaluateItemPath(options.filter),

            // GROUPED BY ...
            dctGroups = lstFiltered.reduce(
                function (groups, dctItem) {
                    var v = dctItem.getAttribute(strGroupAttrib),
                        vGroup = strGroupFn ? eval(
                            '"' + v.toString() + '".' + strGroupFn
                        ) : v,
                        dctGroup = groups[vGroup] || {};

                    dctGroup[dctItem.id] = dctItem;
                    groups[vGroup] = dctGroup;

                    return groups;
                }, {}
            ),

            // GROUPS ORDERED BY
            lstGroups = Object.keys(dctGroups)
            .sort(blnZA ?
                function (a, b) {
                    return a === b ? 0 : (a < b ? 1 : -1);
                } : function (a, b) {
                    return a === b ? 0 : (a > b ? 1 : -1);
                });
                

        return "" + strGroup.charAt(0)
            .toUpperCase() + strGroup.slice(1) + ":\n\n" +
            lstGroups.map(function (k) {
                var dctGroup = dctGroups[k];

                return "" + k.substr(0, 7) +
                    '-' + k.slice(8) +
                    ':\n' + Object.keys(dctGroup)
                    .map(function (id) {
                        var item = dctGroup[id];

                        var strTime = timePart(item, strGroup),
                            strProject = itemProject(item),
                            attribs = item.attributes,

                            strPrefixes = (strTime ?
                                '' +
                                strTime +
                                '\t' :
                                '') + (strProject ?
                                '' +
                                strProject +
                                '' : '');

                        return '\t- ' + strPrefixes +
                            // body text
                            item.bodyDisplayString
                           // Any remaining (non-grouping) tags
                            + ' ' + otherTags(
                                item.attributeNames,
                                attribs,
                                strGroup
                            ) + '';
                    })
                    .sort()
                    .join('\n');
            })
            .join('\n\n');
    }

    var ds = Application("TaskPaper")
        .documents,
        varResult = ds.length ? ds[0].evaluate({
            script: fnTP3Context.toString(),
            withOptions: {
                filter: '//@due',
                groupBy: 'due.substr(0, 10)',
                orderBy: 'due'
            }
        }) : false;

    return varResult;
})();


var a = Application.currentApplication(),
    sa = (a.includeStandardAdditions = true, a);

sa.setTheClipboardTo(strClip);

strClip

Thanks a lot. This goes in the right direction!

I have no experience in writing scripts, but I guess I can fine-tune the way that the output appears e.g. by looking into the details of the script, and also by comparing the FoldingText script and this one. Will look into it later.

Thank you again,
Paul

1 Like