MACRO: List All Macros By Size

I appreciate that, but don't worry about bothering me. If I didn't want to be bothered with fixing issues, I wouldn't have posted it in the first place. I've actually chosen to not upload things a few times, because I was worried they'd open a can of worms.

So like I said, if I uploaded it, I want to fix it if it breaks. :slight_smile:

1 Like

Like I said in one of the prior posts, it may have to do with custom icons. Another possibility is the opening comments, since they're RTF.

Whatever the reason, feel free to get rid of any fluff. You might want to keep notes on what you changed, so if I release a new version, you can do it again.

Cool. It still doesn't work for me. I get the same error. Maybe my plist doesn't want to be bothered.

Thanks, @DanThomas.
No complaint at all! That shows how popular your macros are. We are keeping and using them regardless of their sizes.
But thank you very much for pointing to directions that we could reduce their size and show sensitivity for future upgrades. That possibility of future upgrades has kept me from sitting down to mess around with the macros.

PS: I actually wondered one day about your Mac, since I believe you must have more large macros (well, they are actually KM mini apps). :rofl:

1 Like

Version 1.0.1 works for me now. Thanks Dan!

2 Likes

I understand the worry about future updates, but half the fun is fiddling with something until it works like you want it to. So like I said, as long as you keep notes, mess around as much as you want!

PS: I actually wondered one day about your Mac, since I believe you must have more large macros

I have a 2017 27" iMac with a 5K monitor, 64GB ram, and a 2TB fusion drive, as well as a 1TB SSD external drive, and several other external drives for backups and storing video files. I also have an external monitor

I don't really notice any slowdown due to the large macros, however since I've had so many large macros for many, many years, maybe I don't know what I'm missing.

(well, they are actually KM mini apps). :rofl:

It's funny you should mention that. The latest big one, Variable Inspector Prompt, really felt like I was writing an application. It's actually why I kept enhancing it, because I was getting a chance to feel like a real developer again.

Unfortunately it took months away from my YouTube channel, and I really should get back to that. Of course, I use a bunch of KM macros for the editing process, so I'm never far away from KM.

1 Like

Thanks Dan! I'll do that with the HTML. I recently went through and replaced your KMFAM icons with similar icons from the icon chooser which did help to reduce the size.

1 Like

@peternlewis I'm not sure you can answer these questions to any detailed degree, because it depends on hardware, but I'll ask anyway:

  1. How much does the size of the macros affect performance? I'm talking about size with respect to custom icons, embedded images, and maybe a large HTML embedded script - things like that. And I'm not sure how I would define "performance"... The editor? Running macros in general?

  2. What if you don't run large macros very often? What kind of effect would they have then, when they're not running?

  3. Add any other thoughts you might have on the subject.

Like I said, I know this is hard to answer, but since you know the inner workings, I was hoping you could offer some general statements. I guess the most important question would be, if I pared down the size of my macros, would I notice much of a difference? (I don't really have any complaints right now.) I have a reasonably fast machine with 64GB of ram and a fusion drive, so I'm not sure how that affects things.

The size of the macros file will affect the editor if it gets too large, because the editor saves the file monolithically each time you make a change.

The size of a macro will also affect the editor performance, since it is designed for macros of a reasonable size, and long macros will require lots of controls and fields and will degrade performance.

The size of the macro file and the size of a macro has little affect on the engine performance.

The size of a macro has little affect on the engine.

Other than the editor, there should be little advantage in making macros or the macro file smaller or shorter.

In the editor, you can judge for yourself whether there is a performance issue when editing macros.

5 Likes

Thanks or the answer - it was just what I was looking for. :slight_smile:

FWIW we can also get the listing directly with an XQuery over the plist XML:

XQuery listing of KM Macros by decreasing size.kmmacros (8.7 KB)

Expand disclosure triangle to view JS Source
(() => {
    "use strict";

    // Rob Trew @2022
    // Ver 0.04

    // Fractionally faster โ€“ skips the plutil command line
    // and temporary file.

    // main :: IO ()
    const main = () => {
        const
            uw = ObjC.unwrap,
            fs = "/following-sibling::",
            item = k => `/key['${k}'=string()]${fs}array/dict`,
            name = `/key['Name'=string()]${fs}string[1]/string()`;

        const xquery = `
            for $g in /plist/dict${item("MacroGroups")}
            let $groupName := $g${name}
            for $m in $g/${item("Macros")}
            let $macroName := $m${name}
            let $size := string-length(string($m))
            order by $size descending
            return concat(
                $size,' ',$groupName,' :: ',$macroName
            )`;

        return either(
            alert("XQuery report over KM Macros plist")
        )(
            xs => uw(xs).join("\n")
        )(
            bindLR(
                readPlistFileLR(kmPlistPath())
            )(
                compose(
                    LRBind(
                        compose(
                            LRBind(
                                xQueryOverDocLR(xquery)
                            ),
                            xmlDocFromStringLR
                        )
                    ),
                    plistFromDictLR
                )
            )
        );
    };


    // ---------------- KEYBOARD MAESTRO -----------------

    // kmPlistPath :: () -> IO FilePath
    const kmPlistPath = () => {
        const
            kmMacros = [
                "/Keyboard Maestro/",
                "Keyboard Maestro Macros.plist"
            ].join("");

        return `${applicationSupportPath()}${kmMacros}`;
    };

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


    // applicationSupportPath :: () -> String
    const applicationSupportPath = () => {
        const uw = ObjC.unwrap;

        return uw(
            uw($.NSFileManager.defaultManager
                .URLsForDirectoryInDomains(
                    $.NSApplicationSupportDirectory,
                    $.NSUserDomainMask
                )
            )[0].path
        );
    };


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

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


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


    // plistFromDictLR :: JS Object -> Either String XML
    const plistFromDictLR = jso => {
        const error = $();
        const xml = $.NSString.alloc.initWithDataEncoding(
            $.NSPropertyListSerialization
            .dataWithPropertyListFormatOptionsError(
                $(jso),
                $.NSPropertyListXMLFormat_v1_0, 0,
                error
            ),
            $.NSUTF8StringEncoding
        );

        return xml.isNil() ? Left(
            error.localizedDescription
        ) : Right(
            ObjC.unwrap(xml)
        );
    };


    // readPlistFileLR :: FilePath -> Either String Dict
    const readPlistFileLR = fp =>
        // Either a message or a dictionary of key-value
        // pairs read from the given file path.
        bindLR(
            doesFileExist(fp) ? (
                Right(filePath(fp))
            ) : Left(`No file found at path:\n\t${fp}`)
        )(
            fpFull => {
                const
                    e = $(),
                    maybeDict = $.NSDictionary
                    .dictionaryWithContentsOfURLError(
                        $.NSURL.fileURLWithPath(fpFull),
                        e
                    );

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

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


    // xmlDocFromStringLR ::
    // XML String -> Either String NSXMLDocument
    const xmlDocFromStringLR = xml => {
        const
            error = $(),
            xmlDoc = $.NSXMLDocument.alloc
            .initWithXMLStringOptionsError(
                xml, 0, error
            );

        return xmlDoc.isNil() ? (
            Left(ObjC.unwrap(error.localizedDescription))
        ) : Right(xmlDoc);
    };


    // xQueryOverDocLR :: XQuery String ->
    // XMLDoc -> [String]
    const xQueryOverDocLR = xQuery =>
        // List of XQuery result strings for XQuery over doc.
        doc => {
            const
                uw = ObjC.unwrap,
                e = $(),
                xs = doc.objectsForXQueryError(xQuery, e);

            return xs.isNil() ? (
                Left(uw(e.localizedDescription))
            ) : Right(uw(xs).map(uw));
        };


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


    // LRBind (=<<) :: (a -> Either b) ->
    // Either a -> Either b
    const LRBind = mf =>
        // Flipped version of bindLR
        m => m.Left ? (
            m
        ) : mf(m.Right);


    // compose (<<<) :: (b -> c) -> (a -> b) -> a -> c
    const compose = (...fs) =>
        // A function defined by the right-to-left
        // composition of all the functions in fs.
        fs.reduce(
            (f, g) => x => f(g(x)),
            x => x
        );


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


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

Hi @DanThomas , I just tried to use v 1.0.1 and got the error

Error: Could not convert string to plist. Error: The data couldnโ€™t be read because it isnโ€™t in the correct format.

OS 11.6.3
KM 10.0.2

I don't know what's wrong. Try one of @ComplexPoint's suggestions.

1 Like