Find selected function in Quiver

A first draft of a macro for looking up the selected function in Quiver.

I use a set of c. 350 generic functions (in JS, with a parallel set in AS) to compose any scripts that I need.

I already have a KM macro for finding and pasting these functions, but I sometimes also need to move in the opposite direction – to look up and perhaps update the version of a function that I have in a Quiver Notebook.

To test the draft macro below, first update variable values for:

  • The path to the Quiver library, and
  • the name of the Quiver Notebook to search in.

Note that this macro assumes that the function name is the first word (space-delimited) of the Quiver note's name.

i.e. given Quiver note names like those below, it finds strings like fmap and fmapLR, as well as flatten, flip, and floor:

46

Find selected function in Quiver.kmmacros (27.5 KB)

findInQuiver

Javascript source

(() => {
    'use strict';

    const main = () => {
        const
            kme = Application('Keyboard Maestro Engine'),
            kmQVar = k => kme.getvariable('quiver' + k),
            lrUUID = uuidFromPathBookNameLR(
                ...['LibPath', 'Book', 'Function'].map(kmQVar)
            );
        return lrUUID.Left || lrUUID.Right;
    };

    // GENERIC FUNCTIONS ----------------------------------

    // Just :: a -> Just a
    const Just = x => ({
        type: 'Maybe',
        Nothing: false,
        Just: x
    });

    // Left :: a -> Either a b
    const Left = x => ({
        type: 'Either',
        Left: x
    });

    // Nothing :: () -> Nothing
    const Nothing = () => ({
        type: 'Maybe',
        Nothing: true,
    });

    // 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.Right !== undefined ? (
            mf(m.Right)
        ) : m;

    // bindMay (>>=) :: Maybe a -> (a -> Maybe b) -> Maybe b
    const bindMay = (mb, mf) =>
        mb.Nothing ? mb : mf(mb.Just);

    // doesFileExist :: FilePath -> IO Bool
    const doesFileExist = strPath => {
        const ref = Ref();
        return $.NSFileManager.defaultManager
            .fileExistsAtPathIsDirectory(
                $(strPath)
                .stringByStandardizingPath, ref
            ) && ref[0] !== 1;
    };

    // doesPathExist :: FilePath -> IO Bool
    const doesPathExist = strPath =>
        $.NSFileManager.defaultManager
        .fileExistsAtPath(
            $(strPath).stringByStandardizingPath
        );

    // filePath :: String -> FilePath
    const filePath = s =>
        ObjC.unwrap(ObjC.wrap(s)
            .stringByStandardizingPath);

    // findIndex :: (a -> Bool) -> [a] -> Maybe Int
    const findIndex = (p, xs) => {
        const i = xs.findIndex(p);
        return i !== -1 ? (
            Just(i)
        ) : Nothing();
    };

    // listDirectory :: FilePath -> [FilePath]
    const listDirectory = strPath =>
        ObjC.unwrap(
            $.NSFileManager.defaultManager
            .contentsOfDirectoryAtPathError(
                ObjC.wrap(strPath)
                .stringByStandardizingPath,
                null
            ))
        .map(ObjC.unwrap);

    // readFile :: FilePath -> IO String
    const readFile = strPath => {
        let error = $(),
            str = ObjC.unwrap(
                $.NSString
                .stringWithContentsOfFileEncodingError(
                    $(strPath)
                    .stringByStandardizingPath,
                    $.NSUTF8StringEncoding,
                    error
                )
            );
        return Boolean(error.code) ? (
            ObjC.unwrap(error.localizedDescription)
        ) : str;
    };

    // showLog :: a -> IO ()
    const showLog = (...args) =>
        console.log(
            args
            .map(JSON.stringify)
            .join(' -> ')
        );

    // JXA ------------------------------------------------

    // standardAdditions :: () -> Application
    const standardAdditions = () =>
        Object.assign(Application.currentApplication(), {
            includeStandardAdditions: true
        });

    // MAIN -----------------------------------------------

    // uuidFromPathBookNameLR :: String -> String -> String
    //                              -> Either String UUID
    const uuidFromPathBookNameLR = (strPath, bookName, fName) => {
        const fp = filePath(strPath);
        return bindLR(
            doesPathExist(fp) ? (
                Right(fp)
            ) : Left(`Quiver library not found at ${fp}`),
            fp => {
                const
                    xs = listDirectory(fp),
                    mbBook = findIndex(
                        x => {
                            const fpMeta = `${fp}/${x}/meta.json`;
                            return doesFileExist(fpMeta) &&
                                bookName === JSON.parse(
                                    readFile(fpMeta)
                                ).name;
                        },
                        xs
                    );
                return bindLR(
                    mbBook.Nothing ? Left(
                        `Notebook named '${
                            bookName
                        }' not found ...`
                    ) : Right(fp + '/' + xs[mbBook.Just]),
                    fpBook => {
                        const
                            notes = listDirectory(fpBook),
                            mbNote = findIndex(
                                noteName => {
                                    const
                                        fpMeta = `${fpBook}/${
                                            noteName
                                        }/meta.json`;
                                    return doesFileExist(fpMeta) &&
                                        fName === JSON.parse(
                                            readFile(fpMeta)
                                        ).title.split(' ')[0]
                                },
                                notes
                            );
                        return mbNote.Nothing ? Left(
                            `'${fName}' not found in ${bookName}`
                        ) : Right(notes[mbNote.Just].slice(0, -7));
                    }
                );
            }
        );
    };

    // MAIN ---
    return main();
})()
1 Like

( added – macro updated above – a message for the case in which no Notebook is found by the name supplied. )

(There are parallel messages for the cases when the library is not found at the given path, or the function not found, as spelled, in the specified notebook)