Best Approach for Inheritance in Script Bundles

Hi,

I’m relatively new to AppleScript but have decades of experience with other object-oriented languages. I’ve been trying to get more knowledgeable with script bundles in order to package my code, but wanting to automate the packaging I looked to osacompile, which can create a scptd file from the .applescript source files I specify. So far, so good.

I’ve run into the problem of how to refer to other scripts, in particular a “super class” that my other script objects would declare as a parent. I’m keen to keep each script definition in its own file. So the child or subclasses would need to, I think, have a ‘use script “ParentObject”’ statement at the top, before the “property parent: ParentObject” statement. It seems like osacompile doesn’t or can’t figure out my script object dependencies from a given list of files.

Today I saw something briefly on loading a script object. Not sure if that’s the way forward. Can anyone help me better understand how to go about bundling an hierarchical object-oriented code base as a scptd bundle, and the mechanics of getting the code to compile without manual intervention like, the sometimes suggested, “just drag-n-drop your scripts into the Script Editor resource drawer”.

One other related question, because there just seems to be almost no obvious official documentation on this, is how to use MyBundle.scptd/Contents/Resources/Scripts folder? Is it purely for drag n drop usage? osacompile just mashes all input files into a main.scpt.

Even if you can suggest somewhere I can find more info on using these features. Even books I see selling for £70+ don’t seem to approach this topic of a collection of single object .applescript files that implement inheritance between them, bundled using osacompile.

Thanks,
Ray

Such a thing doesn’t exist – organization is not osacompile’s role.

I presume you’ve read the relevant section of the ASLG on inheritance:
https://developer.apple.com/library/archive/documentation/AppleScript/Conceptual/AppleScriptLangGuide/conceptual/ASLR_script_objects.html#//apple_ref/doc/uid/TP40000983-CH207-SW15

Honestly, I reckon you’re better to forget about using traditional inheritance, which can get very squirrelly in AppleScript, and just use AppleScript’s library loading mechanism to load code (the “use script…” statement).

I don’t know what you want to make using osacompile. I will show workaround to create bundle using Apple-script. The steps it should perform:

  1. Script creates root folder, gives some name to it, for example “TextManipulationLibrary” (using Finder, or do shell script, or AsObjC)

  2. Script creates in the root folder subfolders structure. (for exampe, “AppendingHandler, TrimmingHandler”, “ChangingCaseHandler”)

  3. Script reads .applescript files from hard disk using repeat loop , compiles them and saves to appropriate locations (to root folder and its subfolders). Exists script examples to compile the .applescript files, and save as .scpt files on this site.

  4. Script adds to root folder extension “.scptd”

Done. Your bundle is ready for use

Start from step 1. Show us your script, if you don’t mind. As I think: Path of parent script should be parent folder of child script in the bundle. This way you can define dependenses. Then to find superclass of current script you search for script of parent folder of current script.

Inheritance example in the AppleScript.


script A
	property animal : "Dog"
	script B
		property |color| : "White"
		on isInherited()
			return (animal is "Dog")
		end isInherited
	end script
end script

|color| of B of A of me
--> "White"

isInherited() of B of A of me
--> true. That is, it is inherited

parent of B of A of me --> «script A»

-- |color| of A of me --> error. Because it is incapsulated in the B script

Other way structured example. You osacompile can control (set) parent property of every inserted script:


script parentScript
	property parent : AppleScript
	property animal : "It was dog"
	on displayDialog()
		display dialog "No, it was cat"
	end displayDialog
end script

script childScript
	property parent : parentScript
	on displayDialog()
		continue displayDialog()
	end displayDialog
	animal of parent
end script

display dialog (run childScript)
displayDialog() of childScript
display dialog "No, I am sure, " & (animal of parent of childScript)

Hi Shane,

So even though osacompile can create a scptd file, it’s really not designed for building packages. I should perhaps construct the package myself?

Yeah I’ve read through the Guide several times, but it does leave one with questions. I’m busy reading your book, by the way. Thanks for the insights, it’s well worth the money.

Hi KniazidisR,

Thanks for the detailed explanations. I was wondering about perhaps constructing the script bundle myself if osacompile can’t create anything other than a single main.scpt file.

I haven’t read of any AppleScript dependency mechanism that relies on a script’s relative path to another script being used to define dependencies before. I’ve used the normal “/Script Libraries” folder locations but that’s just ensuring they are visible to other scripts you’re compiling.

When and how do you “search for script of parent folder”? Is this to manually load a script file that has the parent script defined in it?

Can you explain a little further about how to do this please? Are you referring to the -e command option? I read somewhere that the main.scpt file has access to the script objects placed in the ./Scripts directory, but that was just one sentence on the topic, and I can’t recall where I read it in the many different resources I’ve looked at over the last week.

Yes. You create the basic .scptd bundle with it’s main.scpt file, and then add what else you want. You can add a /Contents/Resources/Script Libraries folder, for example, or scripts you load with load script. Or even third-party frameworks.

Typically, though, a better workflow is to keep things like script libraries in the central repositories, perhaps adding them to the bundle if/when you plan to deploy them on other machines.

Hi Shane,

This is exactly the approach I’m designing for, working on a build system for AppleScript packages. I just thought osacompile might help with it’s scptd detection.

FWIW, this is something that Script Debugger 8 (which is in late public beta) is designed to do. You develop your script, and then when you export you have options to embed used libraries and frameworks, as well as convert the libraries (and any other scripts) to run-only.

Developing a system of script-objects (it is like custom file system) is a complex topic for top-level developers.I think that’s why you couldn’t find the literature you were looking for. It would simply be unprofitable to write a book that only a few would buy. I advise you to heed Shane Stanley’s advice and use a ready-made solution (Script Debugger 8, beta).I am not a high-level developer, and solving a problem requires incentive (sufficient monetary reward) and time. And I do other work, programming for me is just a hobby.

There is only fragmentary information about creating a system of script objects.
The beginning of everything is the path of the top-level folder (you can name it, for example, mySystem or myLibrary), which should eventually become a bundle. Then you can create in it local folder structure, and put into subfoldders your .applescript files accordings to logic “what calls what to run”). All paths are founded easy, relative to path of top-level folder.

Having paths, you can create one smart script, which glues first .applescript you indicated and all .applescript of all its subfolders (recursively) to new text file, compiles it, and saves in whatever format.

During the gluing next text (that is, before compilation), it is possible to establish inheritance (this is not possible after compilation). When injecting a new .applescript into the text, your smart script should give it a unique reference (variable) and wrap with block script nextScriptVariable … end script.

After each script nextScriptVariable clause, the smart script should insert a property parent: parentScriptVariable clause. The variable parentScriptVariable must be saved from nextScriptVariable by the smart script during the previous recursion, as you can imagine, in order to use it at the right time.

As you can see, there are difficulties and there are many of them, but in principle, this way you can automatically link a script from a library of .applescript files, independent of libraries.

Note: the step with wrapping the text and setting parent can be eliminated by writing each .applescript in advance (the code is wrapped in a script block, and added a property parent: someVab clause specifying the parent script). For .applescript files of top-level folder you can put property parent: AppleScript. For rest .applescript files you put variable indicated in script Variable clause of script of its parent folder of script objects system.

Hi Shane,

So there’s no official (or other) command line tool to help with package development? I’ll have a look at Script Debugger in due course but I’m so used to using popular build systems and tools that I want to try and get a workable solution together that doesn’t involve manual intervention. Perhaps Script Debugger is scriptable. Perhaps it has a command line interface? Dreaming much?

That would be something cool to add, if not.

Hi KniazidisR,

I see what you’re saying. I was just hoping it would there would be a slightly less convoluted way of going about package development without having to resort to such manual workarounds, but I’m new to this, so I’ll explore your mechanics of package creation using Script Editor and see if I can get something working manually without gluing all the things. That would be a last resort, from the sounds of your explanation.

Thanks for taking the time to explain to me in detail how to go about it. As you said, I’m probably best off taking Shane’s advice. I don’t really want anything to be needlessly complex.

I wonder if you’re not over-complicating things. A bundle is just an arrangement of folders. So apart from creating the basic bundle containing the required items using osacompile or a script editor, the rest is just copying other files you want to include into the right places. Use bash, script the Finder, whatever — it’s just shuffling files and perhaps creating the odd folder.

Depending on your requirements you may need to edit the Info.plist file, although probably not for a non-app bundle.

It is, but not in terms of populating bundles — that would be largely redundant.

All of that said, AppleScript is an odd language. I used to teach courses in it, and generally the people who found it hardest to learn were those with a programming background — they were far more likely to get frustrated with it. For that reason, I reckon starting at the command line is a fairly hairy-chested approach.

:smiley: 20+ languages under my belt and more than a decade of experience with Xcode, and I’d have to say your observation is very accurate. But in my defence, I’ve been forced to live in a world of complications.

But I think things are getting clearer in an AppleScript sense for me.

I’m intimately familiar with bundles from my Xcode programming background, but have no clue if they hold any special meaning for how AppleScript packaging and runtime linking work in practice.

Most compilers understand a source code compilation unit and can find and understand references to other language elements between files (a parent object being defined in a separate stand-alone file for example), but that seems to be something that only happens after deployment to a known library location with AppleScript.

Anyhoo! At least I have a better understanding of what I’m working with. Thanks Shane.

The only relevant paths for a non-app bundle are:

  • /Contents/Frameworks/ Third-party Objective-C frameworks here can be loaded with a use framework statement.

  • /Contents/Resources/Scripts/ This must contain main.scpt. By convention, other scripts are often stored here.

  • /Contents/Resources/Script Libraries/ .scpt and .scptd files here can be loaded via use script

  • /Contents/Resources/ScriptingAdditions/ Now effectively useless unless SIPS is off.

It will also look in/Contents/Resources/ for a .sdef file, with a suitable entry in the Info.plist file; and for a start-up screen file called description.rtf.

Anything else, the normal rules for bundles apply: put stuff where you want, and build your own paths to load it. (And there are some other relevant paths for app bundles).

How would you make use of scripts in the ./Contents/Resources/Scripts/ directory, if not via use script? Would you have to use the load command? All I’ve ever really done in my limited AppleScript usage is stick common code in ~/Library/Script Libraries/.

Yes, using load script. Script libraries are a relatively new addition to the language.