How Do I Get the Count of All Actions Within a Macro?

Aha!

I was trying to get that to work, but it was only returning the UUID of the selected macros.

Subtleties of syntax...

   selectedMacros

   vs

   selectedmacros

That made the difference. Whose clauses are notoriously slow in AppleScript.

Version 1.02 posted above and is the fastest yet.

Your code in post #20 works well, but is just a trifle slower.

I'm still flummoxed by XPath and need to learn how to use it...

1 Like

slow, slower, fastest

The single source of most bugs and system failures :slight_smile:

( The endemic and inordinately costly use of speed as a proxy for quality : - )

True. The first version was quick and dirty. (As a rule I also don't use commas in macro names, but that is just me.) Another way to skin the cat is to just pass the values back to KM separately with setvariable.

tell application "Keyboard Maestro Engine"
    setvariable "localMacroName" to theName ¬
        instance (system attribute "KMINSTANCE")
    setvariable "localNumberOfActions" to nActions ¬
         instance (system attribute "KMINSTANCE")
end tell

My real motivation was to shine a light on tools available in the shell. I often see things on the forum where a sizable collection of KM actions and/or code could be replaced on one line by piping together some sed, grep, awk and the like. The shell is also where you have tools that tell you anything you want to know about the state of the system. As an example I routinely use lsof to see whether a browser download is complete.

In this case there seemed to be enough of a difference in economy to make it worth mentioning.

Although I'll admit that part of the economy for me reflects the fact that command line stuff rolls off of my fingers pretty easily. JXA doesn't yet. We use the tools that we own! :wink:

Well, this one had me in stitches so I thought I'd share the story.

First, it has never occurred to me to find out how many actions were in a macro but I thought it would be a nasty little problem to solve with all that nesting so I read along hoping to glean some juicy AppleScript and JavaScript.

But the command line solution by @JeffHester just tickled my keyboard and I thought why not turn that into a general purpose counter (not just macro actions)?

Well, there's a good reason why not, it turns out. Which is that grep doesn't return the number of matches but the number of matching lines. Which is fine if you're looking for ActionUID which sits on a line by itself in a .kmmacros export.

But as a general purpose tool, you need something that can find more than one match a line and has more robust regexp support. Which naturally made me think of Perl. So I wrote up a macro using Perl and it was fast and accurate and worked on .kmmacros files and everything else.

Then I slapped my head because the Concordance module in Literary Toolbox does exactly the same thing (presuming to ignore case). It's just called Concordance instead of KM Action Counter.

Which just goes to show how undervalued an education in the liberal arts can be. If one only knew what all those English majors know...

3 Likes

@mrpasini Love it! But...

When using grep, \n isn't so much a new line character as it is a string delimiter. You can use sed (and sometimes tr) to put those delimiters anywhere you like, including in front of whatever item you are interested in isolating and counting.

Say, for example, you want to count the number of instances of the letter 'a' in the clipboard. The following does the trick.

pbpaste | sed "s/a/\n&/g" | grep "a" | wc -l

Of course 'a' could be whatever you want it to be, including the contents of a shell variable, which can be the value of a KM variable.

pbpaste | sed "s/$KMVAR_varName/\n&/g" | grep "$KMVAR_varName" | wc -l

(You do have to worry about special characters and the like, but that is always the case.)

A somewhat more interesting case. Suppose that you want to know not only how many actions there are in a macro, but wanted a complete list of XML key types present along with the number of each.

pbpaste | sed "s/<key>/\n&/g" | grep "<key>" | awk -F'<key>|</key>' '{print $2}' | sort | uniq -c

sed "s/<key>/\n&/g" puts every instance of "<key>" at the start of its own line.
grep "<key>" finds those lines.
awk uses regular expressions as field separators, so awk -F'<key>|</key>' '{print $2}' prints the text between <key> and </key>.

sort alphabetizes the result and passes it on to uniq -c, which prints out each unique word along with the number of times it was found. Among those results will be something like "95 ActionUID", which is the number of actions.

The suite of UNIX command line tools grew up kind of organically over the course of the last 50 years or so as people had to tackle exactly these sorts of problems, among others. A long time ago I stopped asking "can you do such and such?" because the answer almost always turned out to be, "yes."

A statement, I have been delighted to discover, that is also true of Keyboard Maestro.

4 Likes

Hey Folks,

pbpaste | sed 's/<key>/\n&/g' # | grep '<key>' | awk -F'<key>|</key>' '{print $2}' | sort | uniq -c

This script doesn't work as you'd expect on older versions of macOS (pre Big Sur?) where sed doesn't parse the newline character. (Apple only recently updated their command line tools to reasonably new versions.)

The script actually does accomplish its mission on Mojave, but unfortunately that's by accident. An "n" character is inserted in the output rather than a newline character, but this error is made moot since the XML keys are already on lines of their own.

To get the script working as expected you need to install gnu-sed [1] and substitute gsed for sed in the script.

OR

Wrap the old sed command in a C-String to enable the newline character:

pbpaste | sed $'s/<key>/\\\n&/g' | grep "<key>" | awk -F'<key>' '{print $2}' | sort | uniq -c

Then again – you can do without sed altogether and shorten up the script a bit:

pbpaste | awk -F'<key>|</key>' '/<key>/ { print $2 }' | sort | uniq -c

[1] Package Managers for macOS capable of installing gsed:

MacPorts
Homebrew


-Chris

3 Likes

Nitpick: grep as used returns matching lines -- it's wc -l that counts them.

Not as pointless a nitpick as it looks, because an even simpler solution suggests itself. Copy fish dog fish cat fish cow to the clipboard then run:

pbpaste | grep -o "fish" | wc -l

Bish, bash, bosh...

Totally agree with this, and I really should get better with awk, sed, etc.

2 Likes

I happen to have a fondness for awk, and I've used the shell with admiration since the early 80s
but more generally I find in practice that the shell costs much more wasted time experimenting to get things right.

Why ? Everything is a string. This is not a strength, and it costs, in practice, a lot of time :slight_smile:

I find now that I can get to a solidly working solution (which won't trip me up and distract me) very much faster in a more strongly typed context, for example a language in which Bool ≠ Int ≠ String ≠ Array ≠ Dict etc etc, and you get helpful messages when the type is wrong.

There are more helpfully typed versions of the shell environment, of course. See, for example:

Turtle.Tutorial

and

Shelly.hs/README.md at master · gregwebs/Shelly.hs

2 Likes

This is really what I was referring to when I said shell tools "grew up kind of organically." Sometimes that's a feature. Sometimes it's a bug.

Agreed. For example, awk is a really amazing tool that warrants the books that have been written about it. But it won't be my tool of choice if I am doing much more than parsing a string and generating some nicely formatted output. Most of my awk code fits on one line and includes a printf statement somewhere.

The thread started out with a well-defined question, "How do I get the count of all actions in a macro?" The problem is well constrained, you can make assumptions about what the XML generated by KM looks like, and so on. Given that context, a very short, reliable, and easily debugged bit of command line code "does the job."

But what does "does the job" mean, and what kind of tool does it demand? There are some fun and interesting philosophical questions buried in there. Are you trying to develop a general purpose tool for the world, or are you trying to get an answer to the question so that you can get on with life? A developer tends to think about the world from the first perspective.

No. Let me say that more carefully. A developer needs to think about the world from the first perspective.

On the other end of things, a scientist usually thinks about the world from second perspective. The time spent developing the bulletproof code is a distraction from the need for a tool that is useful here and now, gives an answer, and lets one get on with the task at hand. (While I have spent time in the first of those worlds, I live in the second.)

One of the things that I really love about Keyboard Maestro is that it plays beautifully across that spectrum.

4 Likes