N Most Recent Macros (run, edited, or created)
N Most Recent Macros (run- edited- or created).kmmacros (12 KB)
Expand disclosure triangle to view JS source
const
dateType = kmvar.local_dateType,
nMRU = Number(kmvar.local_nMostRecent);
return (() => {
"use strict";
// N most recent macros in terms of
// last run, last edited, or last created.
// modificationDate
// creationDate
// usedDate
// Rob Trew @2024
// Ver 0.2
// dateType drawn from {"created", "edited", "used"}
const main = () =>
either(
alert("First N macros by date of creation edit or use")
)(
xs => {
const
sizeColWidth = Math.max(
...xs.map(x => `${x.size}`.length)
);
return [
`${nMRU} most recently ${toUpper(dateType)}:`,
...xs.map(x => {
const
when = taskPaperDateString(x.someDate),
sizeCell = `${x.size}`.padStart(sizeColWidth, " ");
return `${x.id}\t${sizeCell}\t${when}\t${x.groupName} :: ${x.macroName}`;
// OR without id
// return `${when}\t${x.groupName} :: ${x.macroName}`;
})
]
.join("\n")
})(
mostRecentNMacrosLR(dateType)(nMRU)
);
// ----------------- KM MACRO DATES ------------------
// mostRecentNMacros:: ("created" | "edited" | "used") ->
// Int -> String
const mostRecentNMacrosLR = dateType =>
n => {
const
dateTypes = {
"created": "creationDate",
"edited": "modificationDate",
"used": "usedDate"
},
keys = Object.keys(dateTypes);
return fmapLR(
macros => sortDownOn(
dict => dict.someDate
)(
zipWithN(
id => groupName => macroName =>
someDate => size =>
({
id,
groupName,
macroName,
someDate,
size
}),
macros.id(),
macros.macroGroup.name(),
macros.name(),
macros[dateTypes[dateType]](),
macros.size()
)
)
.slice(0, n)
)(
dateType in dateTypes
? Right(Application("Keyboard Maestro").macros)
: Left(`dateType should be drawn from {${keys}}`)
);
};
// ----------------------- JXA -----------------------
// alert :: String => String -> IO String
const alert = title =>
s => {
const sa = Object.assign(
Application("System Events"), {
includeStandardAdditions: true
});
return (
sa.activate(),
sa.displayDialog(s, {
withTitle: title,
buttons: ["OK"],
defaultButton: "OK"
}),
s
);
};
// ------------------- DATE STRING -------------------
// taskPaperDateString :: Date -> String
const taskPaperDateString = dte =>
[...second(t => t.slice(0, 5))(
iso8601Local(dte).split("T")
)].join(" ");
// --------------------- GENERIC ---------------------
// Left :: a -> Either a b
const Left = x => ({
type: "Either",
Left: x
});
// Right :: b -> Either a b
const Right = x => ({
type: "Either",
Right: x
});
// Tuple (,) :: a -> b -> (a, b)
const Tuple = a =>
// A pair of values, possibly of
// different types.
b => ({
type: "Tuple",
"0": a,
"1": b,
length: 2,
*[Symbol.iterator]() {
for (const k in this) {
if (!isNaN(k)) {
yield this[k];
}
}
}
});
// either :: (a -> c) -> (b -> c) -> Either a b -> c
const either = fl =>
// Application of the function fl to the
// contents of any Left value in e, or
// the application of fr to its Right value.
fr => e => "Left" in e
? fl(e.Left)
: fr(e.Right);
// fmapLR (<$>) :: (b -> c) -> Either a b -> Either a c
const fmapLR = f =>
// Either f mapped into the contents of any Right
// value in e, or e unchanged if is a Left value.
e => "Left" in e
? e
: Right(f(e.Right));
// comparing :: Ord a => (b -> a) -> b -> b -> Ordering
const comparing = f =>
// The ordering of f(x) and f(y) as a value
// drawn from {-1, 0, 1}, representing {LT, EQ, GT}.
x => y => {
const
a = f(x),
b = f(y);
return 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));
// iso8601Local :: Date -> String
const iso8601Local = dte =>
new Date(dte - (6E4 * dte.getTimezoneOffset()))
.toISOString();
// second :: (a -> b) -> ((c, a) -> (c, b))
const second = f =>
// A function over a simple value lifted
// to a function over a tuple.
// f (a, b) -> (a, f(b))
xy => Tuple(
xy[0]
)(
f(xy[1])
);
// 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));
// sortDownOn :: Ord b => (a -> b) -> [a] -> [a]
const sortDownOn = f =>
// Equivalent to sortBy(comparing(f)), but with f(x)
// evaluated only once for each x in xs.
// ('Schwartzian' decorate-sort-undecorate).
xs => sortBy(
flip(comparing(x => x[0]))
)(
xs.map(x => [f(x), x])
)
.map(x => x[1]);
// toUpper :: String -> String
const toUpper = s =>
s.toLocaleUpperCase();
// uncurry :: (a -> b -> c) -> ((a, b) -> c)
const uncurry = f =>
// A function over a pair, derived
// from a curried function.
(...args) => {
const
[x, y] = Boolean(args.length % 2)
? args[0]
: args;
return f(x)(y);
};
// zipWith :: (a -> b -> c) -> [a] -> [b] -> [c]
const zipWith = f =>
// A list constructed by zipping with a
// custom function, rather than with the
// default tuple constructor.
xs => ys => xs.map(
(x, i) => f(x)(ys[i])
).slice(
0, Math.min(xs.length, ys.length)
);
// zipWithN :: (a -> b -> ... -> d) -> [a], [b] ... -> [d]
const zipWithN = (f, ...xss) => {
// Generalisation of ZipWith, ZipWith3 etc.
// f is a curried function absorbing at least
// N arguments, where N is the length of xss.
const m = 0 < xss.length
? Math.min(...xss.map(x => x.length))
: 0;
return xss.reduce(
(gs, vs) => gs.map((g, i) => g(vs[i])),
Array.from({ length: m }, () => f)
);
};
// showLog :: a -> IO ()
const showLog = (...args) =>
// eslint-disable-next-line no-console
console.log(
args
.map(JSON.stringify)
.join(" -> ")
);
// MAIN ---
return main();
})();