MACRO Example: Swift Script returning list of variable names from KME

MACRO Example: Swift Script returning list of variable names from KME

I know this has a long way to go, but I'm going to have to leave as soon as the contractors called, and I thought a couple of you would get a kick out of this.

Here's a Swift script that will return a list of variable names from KME.

NOTES:

  1. It takes a long time to compile, when you execute the script, so be patient. One of a list of things to fix.

  2. Considering it takes so long to compile, and assuming I continue down the Swift road, I may end up calling AS to get/set variables, because it compiles in a snap. But the compile time may be something I can fix.

OK, here it is:

Here's the script:

import Foundation
import ScriptingBridge

@objc public protocol SBObjectProtocol: NSObjectProtocol {
	func get() -> AnyObject!
}

@objc public protocol SBApplicationProtocol: SBObjectProtocol {
	func activate()
	var delegate: SBApplicationDelegate! { get set }
}

@objc public protocol KeyboardMaestroEngineGenericMethods {
	optional func delete() // Delete an object.
}

@objc public protocol KeyboardMaestroEngineApplication: SBApplicationProtocol {
	optional func variables() -> AnyObject
}
extension SBApplication: KeyboardMaestroEngineApplication {}

@objc public protocol KeyboardMaestroEngineVariable: SBObjectProtocol, KeyboardMaestroEngineGenericMethods {
	optional func id() // The unique identifier.
	optional var name: String { get } // the variable's name
	optional var value: String { get } // the variable's value
	optional func setValue(name: String, value: String) // the variable's value
}
extension SBObject: KeyboardMaestroEngineVariable {}

let _kme = SBApplication(bundleIdentifier: "com.stairways.keyboardmaestro.engine") as! KeyboardMaestroEngineApplication
let _vars = _kme.variables!() as! NSArray
for _item: KeyboardMaestroEngineVariable in (_vars as! [KeyboardMaestroEngineVariable])
{
	print(_item.name!);
}

Holy Moley! Discourse supports formatting Swift source code. Cool!

It’s ScriptingBridge that slows down the compile. Sigh. Another roadblock. Time to get out the heavy equipment and start digging.

1 Like

Worth trying the OSAKit ? seems fast from JS …

Possibly. I’ll get back to it. Thanks.

Right now, I’m trying to write actual Swift code. :stuck_out_tongue:

Looks like you’re having fun! Good luck! :thumbsup:

When you have time, I’d love to know what advantages Swift has, or you hope it has, over JXA in working with KM, other Mac apps, and OSX.

FWIW this seems to work as a 'Hello World' for OSAKit in Swift:

List KM variables from Swift through OSAKit.kmmacros (18.3 KB)

import OSAKit

func evalOSA(code: String) -> NSAppleEventDescriptor? {

    var error: NSDictionary?
    let oScript = OSAScript(
        source: code,
        language: OSALanguage(forName: "JavaScript")
    )

    return oScript.executeAndReturnError(&error)
}

let s = evalOSA("Application('Keyboard Maestro Engine').variables()" +
                ".map(function (x) { return x.name(); }).join('\\n')")
if s != nil {
    print(s!.stringValue!)
}

Cool. Running AS is just as easy:

import Foundation

let _script = "\"Hello World\""

var error: NSDictionary?
if let scriptObject = NSAppleScript(source: _script) {
	if let _descriptor: NSAppleEventDescriptor = scriptObject.executeAndReturnError(&error) {
		print(_descriptor.stringValue!);
	}
	else if (error != nil) {
		print("error: \(error)")
	}
}

Looks like there’s lots of ways to do this.

Too bad the code for eliminating JXA or AS completely is too slow to compile. But this isn’t horrible.

I guess a couple of simple functions for reading and setting Keyboard Maestro variables from Swift could be neatly wrapped and need only very minimal snippets of JS/AS.

At this point, Swift has disadvantages, because I need to call JXA or AS to talk to KM. I can do it without that, but it compiles way too slow.

For advantages, it’s just that it’s a language I feel really comfortable with. I still have stuff to learn, but it’s fairly easy for me to pick up, because it’s so much like C#. The IDE (Xcode) is pretty good. Debugging and everything else all integrated. Oh - and code folding! Man how I missed that.

And if you ever need to view the contents of a plist file or .kmmacros file, open it with Xcode. Not bad.

Most everything I learn with Swift can be used directly to write OS/X apps or iOS apps. Well, the iOS apps use different libraries, but the language is the same.

Swift is the kind of language that is well-suited for building large applications, with lots of classes and modules. Wouldn’t want to do that in JS - it’s possible, but that’s not really what JS is for. And certainly not what AS is for.

As far as scripting is concerned, other than the automation issue, it’s a “what do you feel most comfortable with” situation.

Oh absolutely. Right now it’s easy to deal with a string result, but anything else is uncharted waters. I’m working on some code to parse other types of results, but it’s not as simple as you might think - on the other hand, it may just be lack of knowledge.

I’ve got this:

func parseDescriptor(desc: NSAppleEventDescriptor)
{
	let _descType: Int = Int(desc.descriptorType);
	
	switch ( _descType ) {
	case typeUnicodeText,
	     typeUTF8Text:
		print("Text >\(desc.stringValue)<");
	case typeFileURL:
		print("FileURL >\(desc.fileURLValue)<");
	case typeAEList:
		print("List")
	//		object = [NSArray scriptingUserListWithDescriptor:self];
	case typeAERecord:
		print("Record")
	//		object = [NSDictionary scriptingUserDefinedRecordWithDescriptor:self];
	case typeSInt16,
	     typeUInt16,
	     typeSInt32,
	     typeUInt32,
	     typeSInt64,
	     typeUInt64:
		print("Int: \(desc.int32Value)");
	default:
		print("Unknown type");
		//		// create NSData to hold the data for an unfamiliar type
		//
		//		bigEndianDescType = EndianU32_NtoB(descType);
		//		NSLog(@"Creating NSData for AE desc type %.4s.", (char*)&bigEndianDescType);
		//		object = [self data];
	}
}

I’m trying to figure out how to parse a list of records. But it’s on the back-burner right now, because I can encode whatever I need in a string, with tabs or other delimiters, so I can probably do anything I need with strings.

But I’m also the type of person who wants things the way I want them. So we’ll see.

@ComplexPoint - Want a challenge? I want some way to parse a KM “Calculation” field to get all variable names in it, if any.

Some examples:

  • “My Variable MOD 20 = 0” would return “My Variable”
  • “Variable 2 + 3” would return “Variable 2”
  • “Variable - 2 + 3” would return “Variable”
  • “MIN(Variable1, Variable 2)” would return “Variable1” and “Variable 2”

You get the idea. Some of the permutations can get downright scary.

Well, the proper way to do that might be to generate a parser from some kind of PEG tool – I’m not sure that a piecemeal attack would give you a very good ratio of effort to reward.

You would, however, have to specify the grammar. Isn’t there a way of using Peter’s existing parser ?

If you’re sure you want to experiment with it, then PEG.js is the first thing that comes to mind:

http://pegjs.org/
http://nathansuniversity.com/pegs.html

I hear you. If it comes to it, I can skip Calculation fields entirely. Since my goal is to find variables used in a macro, chances are pretty good that if you reference a variable in a Calculation field, you probably used it somewhere else in the macro, typically to set it somehow.

Nope. I asked. :slight_smile:

This is totally cool. It’s also not something I’m capable of using, but Peter certainly could - not that he’d need it of course - he mentioned that he’s written OSes before.

Hey @peternlewis, in your spare time, want to take a stab at creating a Peg.js rule set for parsing your Calculation fields to get the names of variables? What, you don’t understand the phrase “spare time”? (A little joke here - I’m sure you’re way to busy for this. Thought you might appreciate the joke, though. Or… perhaps not.)

Not really, no.

1 Like

I was pretty-much kidding, by the way. I wouldn’t really want to take your focus away from more important things.

I probably shouldn’t even have said it, except I thought there was a chance it might trigger something in your mind, like “Oh yeah, I’m using such-and-such standard ruleset, try searching for that”.

Hope I didn’t tick you off. My sense of humor is an acquired taste.