A macro which is:
- Triggered by typing one or more function names, between semi-colons,
- pastes any corresponding source code (found for matching functions in a given JSON file) into the active editor, and
- uses a subroutine which also needs to be installed.
INSTALLATION
- Install both the subroutine and the macro below.
- Edit the path to that of a
.json
file with the format of prelude-jxa/jsPrelude.json at master · RobTrew/prelude-jxa or prelude-applescript/asPrelude.json at master · RobTrew/prelude-applescript - Experiment, in a code editor, with typing one or more function names between leading and trailing semi-colons – case-sensitive for exact match, or lower-case (optionally starting or ending with
*
) for approximate matches.
For example:
;foldTree;
;map fold;
;map fold traverse;
;*lr;
Subroutine – Source of named functions from JSON file.kmmacros (10,1 Ko)
Source of named functions pasted from JSON file.kmmacros (2.7 KB)
Expand disclosure triangle to view JS source
return (() => {
"use strict";
// Rob Trew @2024
// Ver 0.01
// Source, if found, of function name(s)
// in JSON dictionary at given path.
const main = () => {
const
fp = kmvar.local_JSON_path,
search = kmvar.local_Function_names.trim();
return either(
alert("Source of named functions")
)(
source => source
)(
bindLR(
0 < search.length
? Right(
search.split(/[\s,]+/u)
)
: Left("No function name(s) supplied.")
)(
names => bindLR(
readFileLR(fp)
)(
json => bindLR(
jsonParseLR(json)
)(
sourceLR(fp)(names)
)
)
)
);
};
// sourceLR :: FilePath -> [String] ->
// Dict -> Either String String
const sourceLR = fp =>
names => dict => {
const
ks = Object.keys(dict),
lowers = zip(
ks.map(toLower)
)(
ks
),
similars = k => {
const
matchType = k.startsWith("*")
? "endsWith"
: k.endsWith("*")
? "startsWith"
: "includes",
s = k.replace("*", "");
return lowers.flatMap(
ab => ab[0][matchType](s)
? [ab[1]]
: []
);
};
return fmapLR(
xs => xs.join("\n\n\n")
)(
traverseListLR(name => {
const subDict = dict[name];
return Boolean(subDict)
? Right(subDict.code)
: Left(`"${name}" not found in ${fp}`);
})(
names.flatMap(
k => k in dict
? [k]
: similars(k)
)
)
);
};
// ----------------------- 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
);
};
// readFileLR :: FilePath -> Either String IO String
const readFileLR = fp => {
// Either a message or the contents of any
// text file at the given filepath.
const
uw = ObjC.unwrap,
e = $(),
ns = $.NSString
.stringWithContentsOfFileEncodingError(
$(fp).stringByStandardizingPath,
$.NSUTF8StringEncoding,
e
);
return ns.isNil()
? Left(uw(e.localizedDescription))
: Right(uw(ns));
};
// --------------------- GENERIC ---------------------
// Left :: a -> Either a b
const Left = x => ({
Left: x
});
// Right :: b -> Either a b
const Right = x => ({
Right: x
});
// Tuple (,) :: a -> b -> (a, b)
const Tuple = a =>
// A pair of values, possibly of
// different types.
b => ({
"0": a,
"1": b,
length: 2,
*[Symbol.iterator]() {
for (const k in this) {
if (!isNaN(k)) {
yield this[k];
}
}
}
});
// 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);
// 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 it is a Left value.
e => "Left" in e
? e
: Right(f(e.Right));
// jsonParseLR :: String -> Either String a
const jsonParseLR = s => {
try {
return Right(JSON.parse(s));
} catch (e) {
return Left(
unlines([
e.message,
`(line:${e.line} col:${e.column})`
])
);
}
};
// toLower :: String -> String
const toLower = s =>
// Lower-case version of string.
s.toLocaleLowerCase();
// traverseListLR (a -> Either b c) ->
// [a] -> Either b [c]
const traverseListLR = flr =>
// Traverse over [a] with (a -> Either b c)
// Either Left b or Right [c]
xs => {
const n = xs.length;
return 0 < n
? until(
([i, lr]) => (n === i) || ("Left" in lr)
)(
([i, lr]) => {
// Passing an optional index argument
// which flr can ignore or use.
const lrx = flr(xs[i], i);
return [
1 + i,
"Right" in lrx
? Right(
lr.Right.concat([
lrx.Right
])
)
: lrx
];
}
)(
Tuple(0)(Right([]))
)[1]
: Right([]);
};
// unlines :: [String] -> String
const unlines = xs =>
// A single string formed by the intercalation
// of a list of strings with the newline character.
xs.join("\n");
// until :: (a -> Bool) -> (a -> a) -> a -> a
const until = p =>
// The value resulting from successive applications
// of f to f(x), starting with a seed value x,
// and terminating when the result returns true
// for the predicate p.
f => x => {
let v = x;
while (!p(v)) {
v = f(v);
}
return v;
};
// zip :: [a] -> [b] -> [(a, b)]
const zip = xs =>
// The paired members of xs and ys, up to
// the length of the shorter of the two lists.
ys => Array.from({
length: Math.min(xs.length, ys.length)
}, (_, i) => [xs[i], ys[i]]);
// MAIN ---
return main();
})();
Initially prompted by a question in this thread: Import and Use Numbered Code Snippets - Questions & Suggestions - Keyboard Maestro Discourse