DueDates Script for Taskpaper 3?

Yep, I think that’s it.

Here’s a first rough draft, so there may be some glitches to discover.

It aims to handle @repeat tags in the style of the earlier script at:

You could test it initially from Script Editor, using dummy data, and ⌘Z Undo,

and later assign to a keystroke, or put it in the TaskPaper script menu

JavaScript for Automation Source:
(make sure that you scroll down to fully copy all of the lines)

// First ROUGH draft of @repeat tag handling on the model of an earlier script at:
// http://www.hogbaysoftware.com/wiki/DueDates

// TEST with a combination of dummy data and the UNDO key (Command-Z)

// Rob Trew @complexpoint 2016-05-06

(function () {
    'use strict';

    // TASKPAPER CONTEXT

    function TaskPaperContext(editor, options) {

        // What @repeat(UNIT:Quantity) pair do we have here ?
        // unitQuantParsed :: String -> maybe String
        //                -> {unit: maybe String, quant: maybe Integer}
        function unitQuantParsed(strSerial, strMaybeDelimiter) {
            var lstParts = strSerial.split(strMaybeDelimiter || ':'),
                lngParts = lstParts.length,
                strUnit = lngParts > 0 ? lstParts[0] : undefined;

            return {
                unit: strUnit,
                nameInterval: strUnit ? nameInterval(strUnit) : undefined,
                quant: !isNaN(lngParts > 1 ? (
                    lstParts[1]
                ) : undefined) ? (
                    parseInt(lstParts[1], 10)
                ) : undefined
            };
        }

        // Is this a unit like day, month, year etc
        // or a calendar Name like Friday, May etc ?

        // Weekday names -> 7, Month names -> c. 365
        // nameInterval :: String -> Integer
        function nameInterval(strDayOrMonthName) {
            return isNaN(DateTime.parse(strDayOrMonthName)) ? (
                undefined
            ) : (
                DateTime.parse('next ' + strDayOrMonthName) -
                DateTime.parse('last ' + strDayOrMonthName)
            ) / 172800000; // 2 days of milliSeconds
        }

        // Parseable expression for dateTime of next recurrence ?
        // nextExpression :: Item -> String
        function nextExpression(item) {
            var strRepeat = item.getAttribute('data-repeat'),
                dctRepeat = unitQuantParsed(strRepeat),

                // If the task has a @due tag, then all calculations are done
                // relative to that date.
                // Otherwise, they will be calculated relative to today.
                dteAnchor = DateTime.parse(
                    item.getAttribute('data-due') || 'today'
                );

            if (dteAnchor && dctRepeat.unit) {
                var lngInterval = dctRepeat.nameInterval;

                if (lngInterval) {
                    var dteNamed = DateTime.parse(dctRepeat.unit),
                        strBase = (dteNamed < dteAnchor) ? (
                            'next ' + dctRepeat.unit
                        ) : DateTime.format(dteNamed),

                        lngSkipped = dctRepeat.quant ? (
                            dctRepeat.quant - 1
                        ) : 0;

                    return lngSkipped ? (
                        (strBase + ' +' + lngSkipped.toString()) +
                        (lngInterval === 7 ? ' weeks' : (
                            lngInterval > 360 ? ' years' : ' days'
                        ))
                    ) : strBase;

                } else return DateTime.format(dteAnchor) + ' +' +
                    (dctRepeat.quant || 1) + ' ' + dctRepeat.unit

            } else return undefined;
        }


        // MAIN

        var outline = editor.outline,
            lstUpdateable = outline.evaluateItemPath(
                '//@repeat and (@done or not @due)');


        if (lstUpdateable.length > 0) {
            var result = undefined;

            outline.groupUndoAndChanges(function () {
                result = lstUpdateable
                    .map(function (item) {
                        var strNext = nextExpression(item);

                        if (strNext) {
                            var dteNext = DateTime.parse(
                                strNext
                            );

                            if (!isNaN(dteNext)) {
                                item.removeAttribute(
                                    'data-done'
                                );
                                item.setAttribute(
                                    'data-due',
                                    DateTime.format(
                                        dteNext
                                    )
                                );

                            } else return 'Not parsed: ' + item.bodyString;
                        } else return 'Not parsed: ' + item.bodyString;

                        return strNext;
                    });

            });
            return result;
        }
    };

    // JAVASCRIPT FOR AUTOMATION CONTEXT

    var tp3 = Application('com.hogbaysoftware.TaskPaper3'),
        ds = tp3.documents,
        d = ds.length ? ds[0] : (
            ds.push(tp3.Document()), ds[0]
        );

    return d.evaluate({
        script: TaskPaperContext.toString(),
        withOptions: {
            delimiter: ':'
        }
    });
})();

This seems to work perfectly.

2 Likes