Look Up Text in a List

I have the attached iOS shortcut that looks up selected text in a list, then opens the URL depending on the value of the selected text.

If the selected text is 'Capercaillie' it opens this URL: https://birdsoftheworld.org/bow/species/wescap1/cur/introduction.

If the selected text is 'Black Grouse' is opens this URL: https://birdsoftheworld.org/bow/species/blagro1/cur/introduction

I've scanned through Keyboard Maestro actions and forum and cannot see an equivalent way of doing in Keyboard Maestro, although I imagine it's possible. Apologies in advance if I have missed anything obvious.

Is there an equivalent way of doing this in Keyboard Maestro?

I may be missing a more direct approach, but here, FWIW is one of the routes which start with text in JSON dictionary format like:

{
	"Capercaillie": "https://birdsoftheworld.org/bow/species/wescap1/cur/introduction",
	"Black Grouse": "https://birdsoftheworld.org/bow/species/blagro1/cur/introduction",
	"Severtzov's Grouse": "https://birdsoftheworld.org/bow/species/sevgro1/cur/introduction"
}

i.e.

  • enclosed in { }
  • each line (key-value pairing) separated by a comma,
  • each key and string value in "double quotes"
  • each key: value separated by :

Once you have your dictionary in the JSON exchange format, you can either use it as a KM Dictionary (one approach below), or use it in an Execute JavaScript for Automation action.

Lookup.kmmacros (20.0 KB)

And for another approach, see here:

Hey Ross,

Keyboard Maestro's Prompt With List action is designed to make this sort of thing simple.

image

All you have to do is separate your target text from the descriptive text in the list with a double-underscore.

The result of the above will be the associated URL.

-Chris


Pick from a Text List Example v1.00.kmmacros (6.6 KB)

2 Likes

and if we needed to obtain that value__key format from a JSON source:

Lookup 2.kmmacros (22.8 KB)

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

    // value__key lines for KM 'Prompt from List' actions
    // obtained from a JSON dictionary of key:value pairs.

    const main = () =>
        either(
            alert("Prompt list from JSON dictionary")
        )(
            x => x
        )(
            bindLR(
                jsonParseLR(
                    Application("Keyboard Maestro Engine")
                    .getvariable("jsonDict")
                )
            )(
                dict => Right(
                    Object.keys(dict).map(
                        k => `${dict[k]}__${k}`
                    )
                    .join("\n")
                )
            )
        );

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

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

    // jsonParseLR :: String -> Either String a
    const jsonParseLR = s => {
        // Either a message, or a JS value obtained
        // from a successful parse of s.
        try {
            return Right(JSON.parse(s));
        } catch (e) {
            return Left(
                `${e.message} (line:${e.line} col:${e.column})`
            );
        }
    };

    return main();
})();
1 Like

Yes. Chris @ccstone showed you a great way if you need for the user to pick the shortcut. I use this method in a number of my Macros, and it works quite well, even with large (>1,000) lists in a file.

If you need to find the text for a given key, you can use a simple list (can be KM Variable or file) of one line for each key:
<key>TAB<text>

and then search this list using RegEx.
I have a number of Macros that use this method, and it is easy to use, and very fast.
Let me know if you'd like more info.

1 Like

Hi thanks for reply. I possibly didn't make it clear, but I don't want Keyboard Maestro to prompt me a for bird species. Instead I want Keyboard Maestro to use text I have selected as the bird species. Hopefully the attached screenshot makes it more clear.

I have spent last hour in Keyboard Maestro trying to figure out how to do this, but haven't been successful so far. Any advice would be appreciated.

Here is one approach:

Lookup from selected word.kmmacros (24.0 KB)

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

    ObjC.import("AppKit");

    // main :: IO ()
    const main = () =>
        either(
            alert("Open page from clipboard")
        )(
            url => url
        )(
            bindLR(
                clipTextLR()
            )(
                clip => bindLR(
                    jsonParseLR(
                        Application("Keyboard Maestro Engine")
                        .getvariable("jsonBirds")
                    )
                )(
                    dict => {
                        const
                            url = dict[
                                toTitle(clip).trim()
                            ] || "";

                        return url.startsWith("http") ? (
                            Right(url)
                        ) : Left(`No link found for '${clip}'`);
                    }
                )
            )
        );

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

    // clipTextLR :: () -> Either String String
    const clipTextLR = () => (
        v => Boolean(v) && 0 < v.length ? (
            Right(v)
        ) : Left("No utf8-plain-text found in clipboard.")
    )(
        ObjC.unwrap($.NSPasteboard.generalPasteboard
            .stringForType($.NSPasteboardTypeString))
    );


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

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

    // jsonParseLR :: String -> Either String a
    const jsonParseLR = s => {
        // Either a message, or a JS value obtained
        // from a successful parse of s.
        try {
            return Right(JSON.parse(s));
        } catch (e) {
            return Left(
                `${e.message} (line:${e.line} col:${e.column})`
            );
        }
    };

    // toTitle :: String -> String
    const toTitle = s =>
        // NB this does not model any regional or cultural conventions.
        // It simply simply capitalizes the first character of each word.
        words(s)
        .map(w => w[0].toUpperCase() + w.slice(1).toLowerCase())
        .join(" ");

    // words :: String -> [String]
    const words = s =>
        // List of space-delimited sub-strings.
        s.split(/\s+/u);

    return main();
})();

Yep works as required. Many thanks.

1 Like

I like JSON strings, but don't find them that useful in KM. I use them mostly in JavaScripts.

IMO, it is easier to build and maintain a simple tab-delimited list than a complicated JSON string.
So, here is another solution that uses a tab-delimited list with RegEx (which is very fast) to lookup the text value for a given key.
I have also provided some options that are disabled for setting the source string and for selecting the key.

Below is just an example written in response to your request. You will need to use as an example and/or change to meet your workflow automation needs.

Please let us know if it meets your needs.

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Example Output

MACRO:   Replace Key with Text [Example]

-~~~ VER: 1.0    2021-03-16 ~~~
Requires: KM 8.2.4+   macOS 10.11 (El Capitan)+
(Macro was written & tested using KM 9.0+ on macOS 10.14.5 (Mojave))

DOWNLOAD Macro File:

Replace Key with Text [Example].kmmacros
Note: This Macro was uploaded in a DISABLED state. You must enable before it can be triggered.


ReleaseNotes

Author.@JMichaelTX

PURPOSE:

  • Replace Key with Text [Example]
    • Demo how to use simple tab-delimited list with RegEx to find the text for a given key

HOW TO USE

  1. First, make sure you have followed instructions in the Macro Setup below.
  2. See the below "How to Use" Comment Action
  3. This macro is just an example written in response to your request. You will need to use as an example and/or change to meet your workflow automation needs.

MACRO SETUP

  • Carefully review the Release Notes and the Macro Actions
    • Make sure you understand what the Macro will do.
    • You are responsible for running the Macro, not me. ??
      .
      Make These Changes to this Macro
  1. Assign a Trigger to this Macro .
  2. Move this macro to a Macro Group that is only Active when you need this Macro.
  3. ENABLE this Macro, and the Macro Group it is in.
    • For more info, see KM Wiki article on Macro Activation
      .
  • REVIEW/CHANGE THE FOLLOWING MACRO ACTIONS:
    (all shown in the magenta color)
    • SET Source String
      • Tab-delimited list of Key TAB Text

REQUIRES:

  1. KM 9.0+ (may work in KM 8.2+ in some cases)
  2. macOS 10.12.6 (Sierra)+

TAGS: @Example @RegEx @LookUp @Lists

Hey Ross,

If I was to use JSON for this sort of thing, I'd use Keyboard Maestro's built-in lookup mechanism – the only thing that's a trifle complex is getting the clipboard into the lookup token. You have to escape the delimiter characters and use a process tokens filter action for that.

Lookup URL from Selected Word (JSON) v1.00.kmmacros (6.3 KB)

I don't really like to use JSON for simple lookups like this, because it adds significant effort to maintaining a lookup table.

The method I prefer is to use a RegEx search of a tab-delimited text table, which can be in a Keyboard Maestro variable of offloaded to a text file. (For really big tables it's much more convenient to edit a text file than a KM variable.)

Lookup URL from Selected Word (RegEx) v1.00.kmmacros (7.5 KB)

-Chris

1 Like