and another alternative, is of course, a script action of some kind.
In a JavaScript for Automation action, you could use a function like:
// diffLines = String -> String -> [(Int, String, String)]
const diffLines = textA =>
// One-indexed list of any differing lines in
// two given strings.
// Where line counts differ, all surplus lines are
// included in the difference list.
textB => zipWithLong(
(a, b) => a !== b
? [a, b]
: []
)(
lines(textA)
)(
lines(textB)
)
.flatMap(
(ab, i) => 0 < ab.length
? [[1 + i, ...ab]]
: []
);
To generate JSON difference lists like:
[
[
4,
"Sample text line 4... this one is different",
"Sample text line 4... capture this!"
],
[
6,
"Sample text line 6... this matches",
"Sample text line 6... does this match ?"
],
[
8,
"",
"Sample text line 8 ... this one has no pair ..."
]
]
For example:
Find Mismatched Lines by diff.kmmacros (5.2 KB)
Expand disclosure triangle to view JS source
(() => {
"use strict";
// Rob Trew @2023
// Ver 0.1
// ------------------- DIFF LINES --------------------
// diffLines = String -> String -> [(Int, String, String)]
const diffLines = textA =>
// One-indexed list of any differing lines in
// two given strings.
// Where line counts differ, all surplus lines are
// included in the difference list.
textB => zipWithLong(
(a, b) => a !== b
? [a, b]
: []
)(
lines(textA)
)(
lines(textB)
)
.flatMap(
(ab, i) => 0 < ab.length
? [[1 + i, ...ab]]
: []
);
// ---------------------- TEST -----------------------
const main = () => {
const kmv = kmValue(kmInstance());
return JSON.stringify(
diffLines(
kmv("local_Alpha")
)(
kmv("local_Beta")
),
null, 2
);
};
// ---------------- KEYBOARD MAESTRO -----------------
// kmInstance :: () -> IO String
const kmInstance = () =>
ObjC.unwrap(
$.NSProcessInfo.processInfo.environment
.objectForKey("KMINSTANCE")
) || "";
// kmValue :: KM Instance -> String -> IO String
const kmValue = instance =>
k => Application("Keyboard Maestro Engine")
.getvariable(k, {instance});
// --------------------- GENERIC ---------------------
// lines :: String -> [String]
const lines = s =>
// A list of strings derived from a single string
// which is delimited by \n or by \r\n or \r.
0 < s.length
? s.split(/\r\n|\n|\r/u)
: [];
// zipWithLong :: (a -> a -> a) -> [a] -> [a] -> [a]
const zipWithLong = f => {
// A list with the length of the *longer* of
// xs and ys, defined by zipping with a
// custom function, rather than with the
// default tuple constructor.
// Any unpaired values, where list lengths differ,
// are simply appended.
const go = xs =>
ys => 0 < xs.length
? 0 < ys.length
? [f(xs[0], ys[0])].concat(
go(xs.slice(1))(ys.slice(1))
)
: xs
: ys;
return go;
};
// MAIN ---
return main();
})();