One way is, of course, to use a Keyboard Maestro Execute JavaScript for Automation action which partitions the URLS into two lists:
- Problems – the urls which could not be opened
- Opened – the urls which have been successfully opened.
The intention, in the sketch below, is that you read the full text of your CSV file into a variable called local_urlsCSV
After trying to read each url, it aims to return two lists – problems and successes.
Partitions of url CSV- Lists of 1. Unopenable and 2. Successfully Opened.kmmacros (7.3 KB)
Expand disclosure triangle to view JS source
return (() => {
"use strict";
ObjC.import("AppKit");
// ---------------------- MAIN -----------------------
const main = () => {
const urls = lines(kmvar.local_urlsCSV || "");
return [
...bimap(
xs => ["Problems:", ...xs.map(x => ` ${x}`)]
.join("\n")
)(
xs => ["Opened:", ...xs.map(x => ` ${x}`)]
.join("\n")
)(
partitionEithers(
urls.flatMap(s => {
const url = s.trim();
return 0 < url.length
? [openURL(url)]
: []
})
)
)
]
.join("\n\n");
};
// ----------------------- JXA -----------------------
// openURL :: String -> Either String IO String
const openURL = url => (
// A url, wrapped in an attempt to open it.
// ObjC.import('AppKit')
$.NSWorkspace.sharedWorkspace.openURL(
$.NSURL.URLWithString(url)
) ? (
Right(url)
) : Left(`Could not open "${url}"`)
);
// --------------------- 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];
}
}
}
});
// bimap :: (a -> b) -> (c -> d) -> (a, c) -> (b, d)
const bimap = f =>
// Tuple instance of bimap.
// A tuple of the application of f and g to the
// first and second values respectively.
g => tpl => Tuple(f(tpl[0]))(
g(tpl[1])
);
// bindLR (>>=) :: Either a ->
// (a -> Either b) -> Either b
const bindLR = lr =>
// Bind operator for the Either option type.
// If lr has a Left value then lr unchanged,
// otherwise the function mf applied to the
// Right value in lr.
mf => "Left" in lr
? lr
: mf(lr.Right);
// 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);
// first :: (a -> b) -> ((a, c) -> (b, c))
const first = f =>
// A simple function lifted to one which applies
// to a tuple, transforming only its first item.
([x, y]) => Tuple(f(x))(y);
// 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));
// 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)
: [];
// partitionEithers :: [Either a b] -> ([a],[b])
const partitionEithers = xs =>
// A tuple of two lists:
// first all the Left values in xs,
// and then all the Right values in xs.
xs.reduce(
(a, x) => (
"Left" in x
? first(ys => [...ys, x.Left])
: second(ys => [...ys, x.Right])
)(a),
Tuple([])([])
);
// 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])
);
// --------------------- LOGGING ---------------------
// showLog :: a -> IO ()
const showLog = (...args) =>
// eslint-disable-next-line no-console
console.log(
args
.map(JSON.stringify)
.join(" -> ")
);
// sj :: a -> String
const sj = (...args) =>
// Abbreviation of showJSON for quick testing.
// Default indent size is two, which can be
// overriden by any integer supplied as the
// first argument of more than one.
JSON.stringify.apply(
null,
1 < args.length && !isNaN(args[0])
? [args[1], null, args[0]]
: [args[0], null, 2]
);
return main();
return sj(
main()
);
})();