load script and (non-)visibility of properties/handlers

Hi

Background: My ASS project had grown too big to be manageable and so I have broken it up into 10 scripts, 9 of which are intended to be libraries to export properties and handlers to the main script.

I am now wondering if AppleScript does even have a proper concept of libraries (other than OSAXen) because I have run into serious visibility problems when using load script to import library code.

For example:

I have a module A.applescript …

property A : “aaa”

and a module B.applescript …

property B : “bbb”

and then the main script …

set ALib to load script POSIX file of (ALibPath)
set BLib to load script POSIX file of (BLibPath)

so far so good, but here comes the trouble …

module C.applescript …

set foo to (A of ALib)

but ALib won’t be visible in module C unless I load it again which means I have two instances of ALib which defeats the whole purpose of having a library in the first place.

In most other languages you can build a hierarchical library where Main imports from A, B and C, while C also imports from A without any replication of the library.

So, is this something that cannot be done with AppleScript? Have I outgrown AppleScript?

any hints appreciated (but stay open applet isn’t good enough - I need a hierarchical library that will reside within the app I am building)

thanks
rgds
benjk

Hi,

Here’s one way.

Library A:

property a : “aaa”
on ASub1()
return “A1”
end ASub1

on ASub2()
return “A2”
end ASub2

Library B:

property b : “bbb”
on BSub1()
return “B1”
end BSub1

on BSub2()
return “B2”
end BSub2

Library C?:

tell ALib
set c to a of it
end tell
return c

Main Script:

set ALib to (load script file “Macintosh HD:Users:kel:Desktop:ALib”)
set BLib to (load script file “Macintosh HD:Users:kel:Desktop:BLib”)
set CLib to (load script file “Macintosh HD:Users:kel:Desktop:CLib”)
tell CLib to run

This works but may not be proper. I have to review scope of variables in script objects and heirarchy.

gl,

thanks kel.

I didn’t want to have to initialise each dependent upon library from each dependent library, if only for reasons of simplicity and manageability.

However, it seems I found a way that’s workable, like so …

Module MyMainApp.applescript

property VerboseLogging : missing value
property SomeOtherLibraryModule : missing value
property YetAnotherLibraryModule : missing value

on will finish launching theObject
– load all modules
set VerboseLogging to LoadScript(“VerboseLogging”)
tell VerboseLogging to RestoreLoggingMode()

end on will finish launching

on will quit theObject
tell VerboseLogging to SaveLoggingMode()
end will quit

Module VerboseLogging.applescript

property CurrentStatus : false

on SaveLoggingMode()
set (contents of default entry “UseVerboseLogging” of user defaults) to my CurrentStatus
end SaveLoggingMode

on RestoreLoggingMode()
tell user defaults
make new default entry at end of default entries with properties {name:“UseVerboseLogging”, contents:false}
set my CurrentStatus to (contents of default entry “UseVerboseLogging” of my user defaults)
end tell
if CurrentStatus then
log “verbose logging is on”
else
log “verbose logging is off”
end if
end RestoreLoggingMode

SomeOtherLibraryModule

(* requires module VerboseLogging *)
global VerboseLogging

on SomeHandler()
if (CurrentStatus of VerboseLogging) then log “some message”
end SomeHandler

YetAnotherLibraryModule

(* requires VerboseLogging )
global VerboseLogging
(
requires SometOtherLibraryModule *)
global SomeOtherLibraryModule

on SomeHandler()
if (CurrentStatus of VerboseLogging) then log “some message”
SomeHandler() of SomeOtherLibraryModule
end SomeHandler

This works and is manageable. If I can assign handler LoadScript to a property I might even be able to do something like this …

if (VerboseLogging is missing value) then set VerboseLogging to LoadScript(“VerboseLogging”)

… and have this code run automatically when a given dependent module is first used. Now, that would be really perfect then. However, I haven’t gotten that far yet.

BTW, my LoadScript(ModuleName) handler loads scripts by name only (without path and without the “.scpt”) from Contents/Resources/Scripts in the applications’ bundle, which was another pain to get working.

If anybody sees any caveats with the approach I am using here, I’d like to hear about them. Further, if anybody has any ideas how to improve this towards automatically running LoadScript on a required module when the dependent module is first use, I’d like to hear about that, too.

thanks in advance
rgds
benjk

Hi,

Sorry I caouldn’t help, but in your first post, the line:

module C.applescript …

set foo to (A of ALib)

implies that the script needs to run. Instead of this statement, you can write:

tell ALib
set foo to A of it
end tell
return foo

and to set foo you need to run your module C anyway, because it’s a statement in the run handler of the module.

gl,

Sorry my first post was probably too abstract and not detailed enough to explain what I was after.

I didn’t think of

set foo to (A of ALib)

to be in any run handler, cause my libraries don’t have run handlers, they are only collections of properties and handlers which are moved out from the main script in order to make the main script better comprehensible and manageable.

In other words, my library “scripts” aren’t really scripts in their own right, they are just wannabe scripting additions within the scope of the project.

For example, I have some wrappers around things like read file and write file and file exists …

on ReadFile(POSIXfilename)

end ReadFile

on WriteFile(POSIXfilename)

end WriteFile

which are quite obviously called from just about any handler in other library, so I need to be able to do

SomeOtherLibrary

on SomeHandler(…)

set myConf to ReadFile(“/etc/foo/bar.conf”) of FileIO

end SomeHandler

In other words, I want to mimic the functionality …

Import FileIO

content = FileIO.ReadFile(“/etc/foo/bar.conf”)

of various modular languages such as Modula-2 and Python as close as possible.

thanks for the feedback anyway
rgds
benjk

Unlike most scripting languages, AppleScript doesn’t have a built-in system for module management. (Yes this sucks.) You can rig your own or use a third-party alternative: AppleMods’ Loader system is the only one I’m aware of. It’s not as fast or powerful as, say, Python’s, but it’s tested, mature and about as good as you’ll get for AppleScript; certainly sufficient for your needs. See the Getting Started page for the installer and setup instructions.

One teensy drawback with Loader is the final developer docs aren’t done yet (I promised to do these about a month ago, but have been busy with other stuff). I’ve posted the interim docs along with a simple Studio example on my site:

http://freespace.virgin.net/hamish.sanderson/LoaderDocs.sit
http://freespace.virgin.net/hamish.sanderson/MVCExampleProject.sit

To have Loader manage your project’s libraries for you, you first add a header to each of your project’s library scripts that’ll allow Loader to load them as ‘Components’ of that project. (In Loader-speak, a ‘component’ is a library script that’s only available to the project or library that contains it. This helps avoid namespace collisions between different libraries.) You then add a header to your project’s main script that binds Loader and tells it to load the libraries used by your main script. Loader will then automatically resolve all dependencies recursively (it can even handle cyclical dependencies if it meets them).

It’s not quite as elegant or terse as using one-line ‘import FooLib’ statements, but it gets the job done and saves a lot of headaches since writing your own library-binding code is a major chore in anything but the most trivial situations. The header code is mostly straightforward boilerplate so easy enough to write by hand, or you can use the wizards included in the LoaderDocs .sit to generate it for you.

Okay, on to business…

First thing is to make all your libraries Loader-compatible. For example, if you’ve a component named FooLib which has no other dependencies, you’d add the following header to it:

property __name__ : "FooLib"
property __version__ : "1.0.0"
property __lv__ : 1

----------------------------------------------------------------------
-- DEPENDENCIES

on __load__(loader)
end __load__

----------------------------------------------------------------------

-- Your handlers here, e.g.:

on foo(x, y)
	x + y
end foo

Save this as FooLib.scpt in your project’s ‘Components’ folder (typically you’d name this folder ‘Components’ and store it within your application bundle, but it’s up to you).

If you’ve another component, BarLib, which is dependent upon FooLib, here’s how you’d write the its header:

property __name__ : "BarLib"
property __version__ : "1.0.0"
property __lv__ : 1

----------------------------------------------------------------------
-- DEPENDENCIES

property _FooLib : missing value

on __load__(loader)
	set _FooLib to loader's loadComponent("BarLib")
end __load__

----------------------------------------------------------------------

-- Your handlers here

on bar()
	_FooLib's foo(1, 2)
end bar

When Loader loads this component it will automatically assign the FooLib component to the _FooLib property, making FooLib available to BarLib’s handlers. Save this as BarLib.scpt in your Components folder, and so on.

Once you’ve set up all your component scripts, add some additional boilerplate to your project’s main script to bind Loader and tell it to load your components.

First, since you probably want your application to be portable, you should bind an actual Loader object so end-users don’t need to install the Loader library on their systems:

property _Loader : (run application "LoaderServer")'s makeLoader()

Next, add the code to load the components used by your main script, e.g.:

----------------------------------------------------------------------
-- DEPENDENCIES

property _BarLib : missing value

on __load__(loader)
	loader's setComponentsFolder(alias "Path:To:Components:Folder") -- see Important Note below
	set _BarLib to loader's loadComponent("BarLib")
end __load__

Finally, add the code to call the load handler. There are two ways you can do this:

  1. To load everything at compile-time, use the following trick:
property _ : __load__(_Loader)
  1. To load everything at runtime, add the load call to your ‘will finish launching’ handler:
on will finish launching theObject 
	__load__(_Loader)
	... 
end on will finish launching

Important Note: If you use static loading, hard-code the path in your load handler (as shown in the example above). If you use dynamic loading, use Studio’s ‘path for’ command to locate the Components folder at run-time to ensure portability. For Studio projects static loading is generally most convenient - unless you’re [e.g.] using a third-party LGPL-ed library in a closed-source application, in which case you must load that library dynamically to comply with the LGPL licence.

Anyway, HTH. If you need any more help or info on Loader, post here or mail me directly. (Might help motivate me to finish the developer docs.:slight_smile: Also check out some of the other libraries on AppleMods, e.g. the HTMLTemplate library contains a couple more advanced applet-based examples that use Loader’s ‘Components’ feature to dynamically load compiled template objects from disk.

Hi,

basically, Applescriipt is a link between you and applications. You can connect python or whatever with your program but AppleScript is like the intermediary.Note that you lose time when using shell scripts. Speeking about speed, I found a workaround to your usingh subroutines from other scripts. The word was not heirarchy, but inheritance. the basic theory is to make a parent application. then you declare in each library your parent that app.

gl anyways,

I hear you. Despite appearances to the contrary, AppleScript is not the right tool for developing real applications. I have already promised myself that I will learn PyObjC and use Python+Cocoa after I finished this project, then use AppleScript only for mom and pap stuff as clearly this is what it was intended for.

Studio seems to be an absolute overload – so utterly out of line with what AppleScipt is meant for. It feels like you’re driving a Bentley powered by a mountain bike. It’s a really great mountain bike, but it wasn’t meant to power a Bentley, never will be.

At the time it seemed a good idea to use AS Studio because it seemed an easy way to get into building Cocoa apps and back then I didn’t know about PyObjC. In hindsight, the time I spent on finding my way around AppleScript’s bazillion bugs, quirks and inconveniences, I may as well have become proficient in PyObjC but I digress.

For now, I have to stick with it at least until this project is done (approaching 3500 lines of code now, probably ending up way above 5000).

Python is not a shell script language, it is a genuine OO programming language along the lines of Java and it performs a lot better than AppleScript.

If you are talking about a server application that runs standalone to be called from outside (a so called Library Server), this is a very bad idea for a Studio project.

The library server can only serve handlers which do not access any Studio objects. It won’t interact with the Studio supplied handlers such as “on clicked”, “will open”, “will close” etc etc etc, or if you need to address values of buttons, text fields, steppers, progress bars, etc etc etc.

Xcode links all scripts within a Studio project into a single application. So even if you have scripts in your project that don’t even know about each other, they will still end up being part of the same application, just that neither AppleScript nor Studio provide you with any means of letting them talk to each other. That really really sucks and it shows that Studio is a bolt on to AppleScript which wasn’t meant to be.

Consider the following example …

Main.applescript


property DataForManipulationbyInspectorWindow : { whatever }
...
on choose menu theObject
     if the name of theObject is "InspectorWindow" then
          set visible of window "InspectorWindow" to true
     else if ...
          ...
     end if
end choose menu
...

InspectorWindow.applescript


...
on clicked theObject
     if the name of theObject is "SomeDataItem" then
          set SomeDataItem of DataForManipulationByInspectorWindow to (state of button "SomeDataItem")
          (* the above won't work because we have no visibility of DataForManipulationByInspectorWindow *)
     else if ...
          ....
     end if
end on clicked

I have tried to declare DataForManipulationByInspectorWindow as global, but this won’t work because InspectorWindow.applescript is not a script object of Main.applescript.

Of course I can load InspectorWindow.appescript into Main.applescript by using load script in Main’s will finish launching handler, but that will not work either because Interface Builder targets the automatically linked by Xcode copy of InspectorWindow.applescript not the second instance created by the load script command. In any event, even if it worked, it would be rather wasteful to have two instances of the same script in one application where only one of them is used.

This only shows that Apple designers haven’t thought their implementation of Studio through very well. Their attitude would seem to be “This is only for Mom and Pap programming anyway, so let’s not get too fancy here”, but following that philosophy they shouldn’t have developed Studio in the first place.

If Xcode links all the scripts in your Studio project into one single Application anyway, then there should clearly be a mechanism to make those scripts communicate with each other. Data encapsulation is a great thing, but it only makes sense if there is a means to import and export encapsulated objects otherwise it isn’t data encapsulation, it becomes data orphanisation which serves no purpose at all.

The solution would be a Studio-only addition to AppleScript that lets you declare data objects in one script of your Studio project to be visible to other scripts in your Studio project in a similar way as the “global” keyword does for script objects. For example …

Main.applescript


property DataForInspector : { whatever }

Inspector.applescript


use property DataForInspector of Main

This would be very simple to use and fit perfectly into the AppleScript philosophy. Since both Main and Inspector scripts are part of the same Studio Xcode project it is easy for the compiler to resolve and initialise references such as “DataForInspector of Main”, after all they are just pointers.

Any of them AppleScript Studio folks reading this forum??? How about implementing this and ship it with Tiger???

but anyway, as you said, AppleScript is really just a glue-between-applications tool and I have probably outgrown the tool. The future will have to be PyObjC.

thanks
rgds
benjk

That’s not quite correct: Apple events provide the link. Any language that can talk to the Apple Event Manager can control an application: UserTalk, AppleScript, Perl, Python, etc. In the other direction, an application that uses the Open Scripting Architecture API to support attachability can talk to any language that provides the appropriate OSA support.

It’s quite possible to develop real applications using AppleScript and Studio. The question you have to ask yourself is whether it’s worth the effort. AppleScript is dreadfully slow and weak compared to languages such as Perl and Python, and its library support is almost non-existent so any heavy lifting you’ll have to do yourself.

Python’s pretty decent as popular scripting languages go. I switched to it about a year ago, and have slowly been bringing its application scripting support up to AppleScript standards.

Studio’s not a great fit for AppleScript users, but it was quick-n-easy for Apple to implement. A HyperCard/FaceSpan-type environment would have been much more appropriate for this particular user group, but also much more expensive to create.

I’d say Python’s about a magnitude faster. It’s also much deeper, allowing you to do more advanced stuff that’s impossible in AppleScript, and has excellent library support.

If you follow MVC design principles (a good idea on any medium-to-large project), all your GUI-specific code should be localised in the one place (e.g. Application.applescript), so building Model logic and support libraries outside of Studio shouldn’t be too much of a problem. There’s other reasons for not using library servers though, e.g. speed, simplicity and convenience.

The solution would be for Apple to implement a proper module management system in AppleScript, but it’s been over a decade and they’ve not done it yet, so personally I’m not holding my breath. :stuck_out_tongue:

Happens to the best of us. :slight_smile: FWIW, I found the transition from AS to Python was pretty easy; mostly just a matter of getting used to its class-based OO model after a couple years doing prototype-based OO in AS (which I still prefer, incidentally). I was feeling at home after a week and fully up to speed within a month. It’s a nice language to work in, and great libraries and community support. For MacPython-specific help and advice, make sure you subscribe to the MacPython SIG mailing list.

HTH