Regex Multi-Line Help

Hello Folks,

I need some guidance because while I thought I knew how to pull this, I don't and my research, in finding the correct multiline technique, has failed me.

Using regex, I'm trying to pull all the sentences with the word "generate". My regex, using regex.com works fine.

(?:(?<=.)|\A)\s*([^.!?\n]?\bgenerate\b[^.!?\n][.!?])

Here is the text and the matches (in bold), via regexr.com, that I'm aiming for is:

When we generate macros, we are creating automation tasks. AI may build workflows in future iterations. Maybe we can generate a robotic automator. The AI will also train and memorize the grammar structure I have created for them, and even continue generating more correlated grammar structures that I may not have explored yet. The way the generate works is quite ridiculous. Once created, everything works fine. It may be nice to judge but to generate is insane.

The output I'm aiming for is the following (can be in a window or a variable):

When we generate macros, we are creating automation tasks. Maybe we can generate a robotic automator. The way the generate works is quite ridiculous. It may be nice to judge but to generate is insane.

Can someone assist my failing? Here is the janky macro that I gave up on.

***.kmmacros (3.4 KB)

I hope my explanation makes sense. My learning of using Regular Expressions within Keyboard Maestro continues.

Thanks for the assist.
KC

Regex.com can do "global" searches. KM's "Search Using Regular Expression" can't -- it is first-match only.

Have another go using the "For Each" action that uses your regex to extract the matching substrings to a Collection and then glues them back together to generate your output.

1 Like

Wizard of @Nige_S , I can certainly do it the way you discussed. Thank you. It definitely works without any issues. Initially, I thought the solution would have more to do with using the expression "(?m) " in some way, perhaps as part of a broader pattern or context.

In addition, after cleaning up the lines, I also used a shell action with sed.

sed -n '/generate/p'

For those who encounter this issue, here is the macro with two possible solutions.:

*** .kmmacros (5.5 KB)

Summary

Thank you so much for your response.

KC

It really comes down to how you define a "sentence" -- and remember that just because you can do everything in one shot with a complicated regex, there's no shame in using multiple simple KM actions instead!

For example, you could say that sentences are "the bits of text separated by a period and a space". Or multiple spaces, for the philistines who have never read The Mac is not a Typewriter :wink:

So you could "For Each" every sentence, then check if each contains "generate":

*** separated by.kmmacros (6.1 KB)

Image

(Note that we have to tidy up the end of Local_output because of how we're joining the strings together.)

Similarly, if you are going to preprocess each sentence into a line of its own you can use a "For Each: line" instead of a sed:

*** for each line.kmmacros (6.0 KB)

Images

Or you might treat a sentence as "the text from immediately after a period (or the start of the string), up to and including the next (or the end of the string)". That makes it easy to include "and contains the word [Gg]enerate" in your pattern:

*** regex.kmmacros (4.0 KB)

Image

All these methods will include sentences that start with "Generate" -- you may or may not want that!

So many ways to do this, so little time to explore them all. As always, go with what makes sense to you and -- importantly -- what will still make sense in a month's time when you go to tweak the macro.

2 Likes

Ladies and Gentlemen, this is why I call him the Wizard of @Nige_S!

I appreciate your guidance, your time AND the effort!

Thank you!
KC

1 Like

Footnote:

as always, we can use a much simpler regular expression
by refactoring from search-replace to the more natural split.

Only sentences containing "generate".kmmacros (2.6 KB)


Expand disclosure triangle to view JS source
return kmvar.local_Source
    .split(/\.\s+/u)
    .filter(x => x.includes("generate"))
    .join(". ");

Or, of course, for a case-insensitive split and filter:

return kmvar.local_Source
    .split(/\.\s+/u)
    .filter(x => x
         .toLocaleLowerCase()
         .includes("generate")
    )
    .join(". ");
2 Likes

I knew you were lurking due to your LOVE of Regular Expressions! Thank you for providing another JSA option. I appreciate it. I'll definitely store this code. At with least this JSA code, I can comprehend what is happening.

Thanks to you and the Wizard!

KC

1 Like

@ComplexPoint,

As you teach us/me these JXA options, please show how to use a KM Variable within your code.

I see this code but my attempts fail as I don't know where exactly to put it:

var kme = Application("Keyboard Maestro Engine");
var value = kme.getvariable("VariableName");

Using the same text in your local_Source variable. Say you have a defined variable called "Local_Word" and Set Variable to text "Local_Word" is set to "grammar".

image

How do you execute this?

Asking for some friends. :slight_smile:

KC

Read kmvar. as "get the value of the Keyboard Maestro variable", and whatever follows is the name of the variable -- local_Source in the original. So for your new macro you'd use kmvar.Local_Word instead.

"Get the value of the Keyboard Maestro variable named Local_Word."

2 Likes

Just a reminder to use JXA modern syntax.

1 Like

The trick is to:

  1. Click the small chevron to the left of the JSA code text field,
  2. choose Modern Syntax, and
  3. select the names of any variables which you want to use.

action:Execute a JSA – Modern Syntax [Keyboard Maestro Wiki]

If I choose Local_Word and one or two more, I can then inspect the resulting kmvar object by evaluating the following code:

return JSON.stringify(
    kmvar, 
    null, 2
)

Perhaps, for example, kmvar may look something like:

{
  "activeLang": "com.apple.keylayout.French",
  "Application_name": "Mail",
  "Local_Word": "grammar"
}

so we can get the value bound to the name Local_Word by writing:

return kmvar.Local_Word

which is just the usual JavaScript shorthand for the fuller expression:

return kmvar["Local_Word"]

kmvar object in JavaScript code.kmmacros (3.8 KB)

3 Likes

I really appreciate the fabulous lessons that you, The Wizard (@Nige_S) and @_jims have provided! They have been incredibly insightful and informative. I will certainly make it a point to inquire more about JXA and its various functionalities. I plan to thoroughly review this material and keep the code for any upcoming macros that can utilize these actions.

Thank you so much for the time and effort in sharing this valuable information with me. It has truly been a great help!

Take care,
KC

2 Likes

Have I completely misunderstood, then?

I thought that kmvar. was analogous to the AS getvariable, i.e. you were calling for individual variables as and when you wanted them. But, if I'm reading it right (JS n00b alert!), your code says that all (or all selected) variables are passed in the object kmvar when the script instantiates, kmvar being a dictionary of varName:varValue pairs.

So it's actually the equivalent of environment variables in the shell script action. Which would make it even more sensible to only include the KM variables you want to use in the script.

Whereas (stretching even further out of my comfort zone!) the form

var kme = Application("Keyboard Maestro Engine");

var someVarNameStr = kme.getvariable("KMVarNameToGet") || 'Default Value if NOT Found';

...is the JXA variant of the AS getvariable.

Damn, I've a lot to learn...

2 Likes

No, not a runtime fetch-on-demand across an Apple Event interface like that.

Instead, an instantiation, at the initialisation of the JS action, of a temporary JS Object

(bound to the name kmvar, within the scope of the enclosing IIFE)

which is populated, at initialisation, only as specified in the menu behind the small chevron to the left of the JS script text field.

Experiment will reveal, with the help of JSON.stringify, as above, that if you just check Include (All|No) Variables, that choice translates either to a super-populated, or wholly depopulated, kmvar object at the start, with no further changes.


And, of course:

  1. no user-changes to that object are propagated back to Keyboard Maestro's own variable name-space, and
  2. no KM variable name-space changes during the JXA action run cause any update of the kmvar object within the action's code evaluation space.

It's initialised, to your specification, at the start, and that's it.

More efficient, and more secure, than the pre-modern pattern.

(It's now a restricted binding in a local and temporary name-space, rather than a binding of all variable values in a global and persistent one)

3 Likes

And, of course, as you suggest, the Execute Shell Script action also has a small chevron menu in which you can limit which Keyboard Maestro values are bound to $KMVAR_ prefixed shell names.


And we can check which $KMVAR_ names have been created by evaluating something like:

echo ${!KMVAR_*}
3 Likes