Source of named functions pasted from JSON file

A macro which is:

  1. Triggered by typing one or more function names, between semi-colons,
  2. pastes any corresponding source code (found for matching functions in a given JSON file) into the active editor, and
  3. uses a subroutine which also needs to be installed.

INSTALLATION

  1. Install both the subroutine and the macro below.
  2. 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
  3. 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

3 Likes