Getting Timestamp of Most Recently Modified Macro

I found a solution, enlisting my son (a developer in Amsterdam) to modify the solution in Post #2 to give me all macros, sorted by descending modification date, with human readable dates instead of the epoch format. If you're interested, here it is:

(() => {
    "use strict";

    const prettyDate = (t) => new Intl.DateTimeFormat('en-US', { dateStyle: 'full', timeStyle: 'short', timeZone: 'America/Denver'}).format(new Date(Number(t)));
    const messy = (line) => { const p=line.split(' '); return prettyDate(p[0]) + " " + p.splice(1).join(' '); }
    const main = () => {
        const
            km = Application("Keyboard Maestro"),
            macros = km.macros;

        return sortBy(
            flip(compare)
        )(
            zipWith(
                t => k => `${new Date(t).getTime()} -> ${k}`
            )(
                macros.modificationDate()
            )(
                macros.name()
            )
        )
        .map(messy)
        .join("\n");
    };


    // ------------------ GENERICS -------------------
    // https://github.com/RobTrew/prelude-jxa
    // ----------------- JS PRELUDE ------------------

    // compare :: a -> a -> Ordering
    const compare = a =>
        b => a < b ? -1 : (a > b ? 1 : 0);


    // flip :: (a -> b -> c) -> b -> a -> c
    const flip = op =>
    // The binary function op with
    // its arguments reversed.
        1 !== op.length
            ? (a, b) => op(b, a)
            : (a => b => op(b)(a));

    // sortBy :: (a -> a -> Ordering) -> [a] -> [a]
    const sortBy = f =>
    // A copy of xs sorted by the comparator function f.
        xs => xs.slice()
        .sort((a, b) => f(a)(b));


    // zipWith :: (a -> a -> b) -> [a] -> [b]
    const zipWith = f => {
        // A list with the length of the shorter of
        // xs and ys, defined by zipping with a
        // custom function, rather than with the
        // default tuple constructor.
        const go = xs =>
            ys => 0 < xs.length
                ? 0 < ys.length
                    ? [f(xs[0])(ys[0])].concat(
                        go(xs.slice(1))(ys.slice(1))
                    )
                    : []
                : [];

        return go;
    };


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

I've managed to tweak the JavaScript to create a zipWith4 that works if I want to add, say, the size of the macro:

(() => {
    "use strict";

    const main = () => {
        const
            km = Application("Keyboard Maestro"),
            macros = km.macros;

        return sortBy(
            flip(compare)
        )(
            zipWith4(
                t => k => u => v =>
                    `${new Date(t).getTime()} -> ${u} -> ${k} -> ${v}`
            )(
                macros.modificationDate()
            )(
                macros.name()
            )(
                macros.id()
            )(
                macros.size()
            )
        )
        .join("\n");
    };


    // ------------------ GENERICS -------------------
    // https://github.com/RobTrew/prelude-jxa
    // ----------------- JS PRELUDE ------------------

    // compare :: a -> a -> Ordering
    const compare = a =>
        b => a < b ? -1 : (a > b ? 1 : 0);


    // flip :: (a -> b -> c) -> b -> a -> c
    const flip = op =>
    // The binary function op with
    // its arguments reversed.
        1 !== op.length
            ? (a, b) => op(b, a)
            : (a => b => op(b)(a));


    // sortBy :: (a -> a -> Ordering) -> [a] -> [a]
    const sortBy = f =>
    // A copy of xs sorted by the comparator function f.
        xs => xs.slice()
        .sort((a, b) => f(a)(b));


    // zipWith4 :: (a -> b -> c -> d -> e) ->
    // [a] -> [b] -> [c] -> [d] -> [e]
    const zipWith4 = f =>
        xs => ys => zs => zzs => Array.from({
            length: Math.min(
                ...[xs, ys, zs, zzs].map(x => x.length)
            )
        }, (_, i) => f(xs[i])(ys[i])(zs[i])(zzs[i]));


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

That works perfectly. But what I'd really like is the UUID of the group containing the macro, not the size. I tried this for the fourth parameter:

macros.macrogroup().id()

But that failed ... I assume there's some way to get this, but it probably involves a mapping between the two data sets that I just don't know about ... all help appreciated!

-rob.

Wild guess -- is JavaScript case sensitive? KM's dictionary shows it as macroGroup, with a capital "G".

I tried both ways, no joy—it doesn't seem to be, as the JavaScript dictionary in Script Editor shows it's the Macros object, but macros works in the script.

I've also tried macros.macrgroup().id() without luck. Clearly my lack of JS knowledge is the stumbling point here :).

-rob.

macrogroup() contains two problems:

  1. Should be macroGroups for the app or macroGroup for a single macro (consult the SDEF in Script Editor, or Script Debugger), and
  2. calling that method by adding () would lose the reference to a .MacroGroups collection which does have has an id() method, trading it in for just a JS Array (of Macro Group values), which has no such method.

Whereas:

Application("Keyboard Maestro").macroGroups.macros.id()

will evaluate to a list of lists (one list of macro UUIDs for each macroGroup)

and if you want redundancy - one group UUID value for every macro:

Application("Keyboard Maestro").macros.macroGroup.id()

See

OS X 10.10 Release Notes - JXA

1 Like

OMG that's exactly what I needed! Thank you so much—with some work now on my part, this could speed my MacroBackerUpper macro by an order of magnitude or two or ten!

-rob.

1 Like

PS there's already a pre-baked zipWith4 in my library at:

(tho these days for arbitrary scaling of zipping beyond zipWith3 I tend to use compositions of zipWith(x => x), which save having to write new functions)

1 Like