AppleScript: How to Get a List of Macros in a Macro Group without the KM Editor Being Activated?

Example 2 :: listing the macros of a named KM group

Listing the macros of a named KM Group.kmmacros (24.4 KB)

JS Source
(() => {
    "use strict";

    // Reading the KM XML from KMEngine.getmacros()

    // Example two :: listing the macros of a named group.

    // Rob Trew @2021

    // main :: IO ()
    const main = () => {
        const
            kme = Application("Keyboard Maestro Engine"),
            groupName = kme.getvariable("groupName"),
            fpTemp = writeTempFile("km.xml")(
                kme.getmacros({
                    asstring: true
                })
            );

        return either(
            msg => alert("Listing named KM group")(msg)
        )(
            report => report
        )(
            bindLR(
                readPlistArrayFileLR(fpTemp)
            )(
                groups => {
                    const
                        groupIndex = groups.findIndex(
                            group => groupName === group.name
                        );

                    return -1 !== groupIndex ? (() => {
                        const
                            group = groups[groupIndex],
                            macros = group.macros,
                            title = `${groupName} group:`,
                            listing = macros.map(macro => {
                                const
                                    name = macro.name,
                                    n = macro.used,
                                    units = plural("time")(n);

                                return `\t- ${name}    (used ${n} ${units})`;
                            }).join("\n");

                        return Right(`${title}\n\n${listing}`);
                    })() : Left(`No group found with name: ${groupName}`);
                }
            )
        );
    };

    // ----------------------- 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
            );
        };

    // --------------------- 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
    });


    // bindLR (>>=) :: Either a ->
    // (a -> Either b) -> Either b
    const bindLR = m =>
        mf => m.Left ? (
            m
        ) : mf(m.Right);


    // doesFileExist :: FilePath -> IO Bool
    const doesFileExist = fp => {
        const ref = Ref();

        return $.NSFileManager.defaultManager
            .fileExistsAtPathIsDirectory(
                $(fp)
                .stringByStandardizingPath, ref
            ) && 1 !== ref[0];
    };


    // 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 => e.Left ? (
            fl(e.Left)
        ) : fr(e.Right);


    // filePath :: String -> FilePath
    const filePath = s =>
        // The given file path with any tilde expanded
        // to the full user directory path.
        ObjC.unwrap(ObjC.wrap(s)
            .stringByStandardizingPath);


    // last :: [a] -> a
    const last = xs =>
        // The last item of a list.
        0 < xs.length ? (
            xs.slice(-1)[0]
        ) : null;


    // plural :: String -> Int -> String
    const plural = k =>
        // Singular or plural EN inflection
        // of a given word.
        n => 1 !== n ? (
            `${k}s`
        ) : k;


    // readPlistArrayFileLR :: FilePath -> Either String Object
    const readPlistArrayFileLR = fp =>
        bindLR(
            doesFileExist(fp) ? (
                Right(filePath(fp))
            ) : Left(`No file found at path:\n\t${fp}`)
        )(fpFull => {
            const
                e = $(),
                maybeDict = (
                    $.NSArray
                    .arrayWithContentsOfURLError(
                        $.NSURL.fileURLWithPath(fpFull),
                        e
                    )
                );

            return maybeDict.isNil() ? (() => {
                const msg = ObjC.unwrap(e.localizedDescription);

                return Left(`readPlistFileLR:\n\t${msg}`);
            })() : Right(ObjC.deepUnwrap(maybeDict));
        });

    // takeBaseName :: FilePath -> String
    const takeBaseName = fp =>
        ("" !== fp) ? (
            ("/" !== fp[fp.length - 1]) ? (() => {
                const fn = fp.split("/").slice(-1)[0];

                return fn.includes(".") ? (
                    fn.split(".").slice(0, -1)
                    .join(".")
                ) : fn;
            })() : ""
        ) : "";


    // takeExtension :: FilePath -> String
    const takeExtension = fp => (
        fs => {
            const fn = last(fs);

            return fn.includes(".") ? (
                `.${last(fn.split("."))}`
            ) : "";
        }
    )(fp.split("/"));


    // writeFile :: FilePath -> String -> IO ()
    const writeFile = fp => s =>
        $.NSString.alloc.initWithUTF8String(s)
        .writeToFileAtomicallyEncodingError(
            $(fp)
            .stringByStandardizingPath, false,
            $.NSUTF8StringEncoding, null
        );

    // writeTempFile :: String -> String -> IO FilePath
    const writeTempFile = template =>
        // File name template -> string data -> IO temporary path
        txt => {
            const
                fp = ObjC.unwrap($.NSTemporaryDirectory()) +
                takeBaseName(template) + Math.random()
                .toString()
                .substring(3) + takeExtension(template);

            return (writeFile(fp)(txt), fp);
        };

    // MAIN ---
    return main();
})();
2 Likes