Listing KM Variables by Descending Size (And Optionally Clearing Some)

Ver 0.4

Significantly faster menu of KM Variables by descending size.

List KM variables by size, and offer deletion of a selection.kmmacros (27.0 KB)

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

    // LIST KEYBOARD MAESTRO VARIABLES
    // SORTED BY DESCENDING SIZE
    // OPTIONALLY EMPTY THE CONTENTS OF SELECTED VARIABLES

    // CAUTION: NOT UNDOABLE

    // Ver 0.4

    /* * *
    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
    ANY KIND, EXPRESS ORIMPLIED, INCLUDING BUT NOT LIMITED
    TO THE WARRANTIES OF MERCHANTABILITY,FITNESS FOR A
    PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
    SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
    ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
    ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
    OR OTHER DEALINGS IN THE SOFTWARE.
    * * */

    // main :: IO ()
    const main = () =>
        either(
            message => "User cancelled" !== message ? (
                alert("KM Variables")(message)
            ) : message
        )(
            alert("Deleted")
        )(
            bindLR(
                menuOfKMVariablesLR(
                    kmVarsBySize()
                )
            )(
                selectedStrings => deletedWhereConfirmedLR(
                    selectedStrings
                )
            )
        );


    // deletedWhereConfirmedLR :: [String] -> Either String String
    const deletedWhereConfirmedLR = ks => {
        const
            kmVarNames = ks.map(k => k.split(/\t/u)[1]),
            indentedListing = `\t${kmVarNames.join("\n\t")}`;

        return "Delete" !== confirm("Selected KM variables")(
            `Delete:\n\n${indentedListing}`
        )("Delete") ? (
            Left("User cancelled")
        ) : (() => {
            const
                kme = Application("Keyboard Maestro Engine");

            return (
                kmVarNames.forEach(k => {
                    kme.setvariable(k, {
                        to: ""
                    });
                }),
                Right(indentedListing)
            );
        })();
    };

    // kmVarsBySize :: () -> [(String, Int)]
    const kmVarsBySize = () => {
        const
            kme = Application("Keyboard Maestro Engine"),
            kmVars = kme.variables;

        return sortBy(
            flip(comparing(snd))
        )(
            zipWith(
                k => v => Tuple(k)(v.length)
            )(
                kmVars.name()
            )(
                kmVars.value()
            )
        );
    };


    // menuOfKMVariablesLR :: [(String, Int)] ->
    // IO Either String String
    const menuOfKMVariablesLR = varsBySize => {
        const colWidth = varsBySize[0][1].toString().length;

        return showMenuLR(true)(
            `${varsBySize.length} ` + (
                "KM variables by descending size"
            )
        )(
            "⌘-Click for multiple selections"
        )(
            varsBySize.map(
                kv => {
                    const
                        valueString = justifyRight(
                            colWidth
                        )(" ")(snd(kv).toString());

                    return `${valueString}\t${fst(kv)}`;
                }
            )
        )([]);
    };

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

    // confirm :: String -> String -> String -> IO String
    const confirm = title =>
        prompt => buttonName => {
            const sa = Object.assign(
                Application("System Events"), {
                    includeStandardAdditions: true
                });

            sa.activate();
            try {
                return sa.displayDialog(prompt, {
                    withTitle: title,
                    buttons: ["Cancel", buttonName],
                    defaultButton: "Cancel"
                }).buttonReturned;
            } catch (e) {
                return "Cancel";
            }
        };

    // showMenuLR :: Bool -> String -> String ->
    // [String] -> String -> Either String [String]
    const showMenuLR = blnMult =>
        // An optionally multi-choice menu, with
        // a given title and prompt string.
        // Listing the strings in xs, with
        // the the string `selected` pre-selected
        // if found in xs.
        title => prompt => xs =>
        selected => 0 < xs.length ? (() => {
            const sa = Object.assign(
                Application("System Events"), {
                    includeStandardAdditions: true
                });

            sa.activate();
            const v = sa.chooseFromList(xs, {
                withTitle: title,
                withPrompt: prompt,
                defaultItems: xs.includes(selected) ? (
                    []
                ) : [],
                okButtonName: "OK",
                cancelButtonName: "Cancel",
                multipleSelectionsAllowed: blnMult,
                emptySelectionAllowed: false
            });

            return Array.isArray(v) ? (
                Right(v)
            ) : Left("User cancelled");
        })() : Left(`${title}: No items to choose from.`);


    // --------------------- 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 =>
        b => ({
            type: "Tuple",
            "0": a,
            "1": b,
            length: 2
        });


    // bindLR (>>=) :: Either a ->
    // (a -> Either b) -> Either b
    const bindLR = m =>
        mf => undefined !== m.Left ? (
            m
        ) : mf(m.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 => "Either" === e.type ? (
            undefined !== e.Left ? (
                fl(e.Left)
            ) : fr(e.Right)
        ) : undefined;


    // comparing :: (a -> b) -> (a -> a -> Ordering)
    const comparing = f =>
        x => y => {
            const
                a = f(x),
                b = f(y);

            return a < b ? -1 : (a > b ? 1 : 0);
        };


    // flip :: (a -> b -> c) -> b -> a -> c
    const flip = op =>
        // The binary function op with
        // its arguments reversed.
        1 < op.length ? (
            (a, b) => op(b, a)
        ) : (x => y => op(y)(x));


    // fst :: (a, b) -> a
    const fst = tpl =>
        // First member of a pair.
        tpl[0];


    // justifyRight :: Int -> Char -> String -> String
    const justifyRight = n =>
        // The string s, preceded by enough padding (with
        // the character c) to reach the string length n.
        c => s => n > s.length ? (
            s.padStart(n, c)
        ) : s;


    // list :: StringOrArrayLike b => b -> [a]
    const list = xs =>
        // xs itself, if it is an Array,
        // or an Array derived from xs.
        Array.isArray(xs) ? (
            xs
        ) : Array.from(xs || []);


    // snd :: (a, b) -> b
    const snd = tpl =>
        // Second member of a pair.
        tpl[1];


    // sortBy :: (a -> a -> Ordering) -> [a] -> [a]
    const sortBy = f =>
        xs => list(xs).slice()
        .sort((a, b) => f(a)(b));


    // zipWith :: [a] -> [b] -> [(a, b)]
    const zipWith = f =>
        // The paired members of xs and ys, up to
        // the length of the shorter of the two lists.
        xs => ys => Array.from({
            length: Math.min(xs.length, ys.length)
        }, (_, i) => f(xs[i])(ys[i]));

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