Beginner's Introduction to AppleScript Libraries

If you tend to accumulate a large number of scripts and script snippets in your Scripts folder, you probably face this frustration. When tackling a new problem, you know that you wrote (or copied from a thread you saw in a handler that will do just the trick required, but unfortunately, it is buried in some other script in the collection in your Scripts folder. Not wanting to re-invent the wheel, you end up Spotlighting for the lost handler, the name of which you have of course forgotten, and when you find it, you copy and paste it into your new script and modify its arguments for your new problem. Not very efficient. Unfortunately, I do it all the time.

There is an easy way to avoid this repeated search pattern, however, that requires only a modicum of self-discipline: build yourself a library of your favorite handlers in several categories and use them in new scripts by simply referring to them in your library. If you want to share the script, you can always go and get it from the library. I’ve just started to do this, and wish I had started long ago. This tutorial illustrates how to start, and necessarily leaves out a lot of what you can do with libraries.

What is a Library?

Virtually any script can be included in another by “inhaling” it with the following instructions:

property MyLibPath : ((path to scripts folder from user domain) & "Script Library:") as text
property LibSort : load script (MyLibPath & "MultiSort.scpt") as alias

Now lets suppose that MultiSort.scpt contains (if reasonably well named) nothing but handlers for sorting a list, e.g., to BubbleS, to SimpleS, to QuickS, etc. (There are good examples of high-speed sort handlers in in this thread in Code Exchange which is part of the bbs referred to above.) Then assuming that we had included the two lines of AppleScript above early in our script, we could refer to any of our sorters in a new script like this:

set NewList to LibSort's BubbleS(ListToSort)

This would have exactly the same effect as copying that handler from MultiSort.scpt and pasting it into our new one, without actually doing that directly, though we have, of course, done it indirectly. This can be a problem sometimes because AppleScript remembers variables and your library can change them - it’s part of your script.

Some Examples

As a simple example of this, suppose that we often had to sort two lists on the basis of the content of one of them while keeping the other in the same order, sometimes wanted to randomize a list or two, and remove items from one or two lists. We can put all of those handlers into one script, save it in our library, and recover it by reference in any other script. The last script in this tutorial is a demo library of handlers; I’ve put it there so it will not occupy the entire first screen of this tutorial.

You should open that script in your default script editor by clicking on the link > Open this Scriptlet in your Editor: at the head of that script, and then save it to a new folder in your user’s Scripts folder. The folder should be called: “Script Library”. If you already have one, fine; just save the script above (as a script) in it, and call it “MultiLists”.

Library scripts can contain handlers that call others in the same script with the usual caveat of remembering to use my in the reference to them. Look at these now. The last of the handlers is a generic one for removing an item in a given list, and the last just calls it twice since there is no problem in keeping the order of the removal in two (or more) lists.

Because we’re absolutely certain to forget how to use these scripts later, I’ve included a first handler that does nothing more than provide the text for a “help” dialog. After you’ve moved the library file as instructed above, run this:

property Lib : (path to scripts folder from user domain) & "Script Library:"
property myLib : load script ((Lib as text) & "MultiLists.scpt") as alias
display dialog myLib's helpMe()

Looking further at MultiLists, you might ask why we need sort2()?. Suppose you had extracted a bunch of event summaries and their dates in two lists in a tell application “iCal” block like the one below, and you wanted to sort them by date while keeping the the corresponding events corresponding with their dates. iCal doesn’t keep them that way - they’re in the order entered, and only one copy of a repeated item is kept, so a double Sort does that.

-- Sample iCal script that generates two lists.
-- Won't work for most readers. my iCal is set up for it. - just illustrative.
set today to (current date)
tell application "iCal"
	close window 1 -- don't need to watch it work and telling opens it.
	set MCals to every calendar whose title contains "Medical" -- a list
	set mCal to item 1 of MCals -- the first is the only in this list.
	-- Collect the date/name list as mdCal
	set mCount to count events of mCal
	set mdEvent to {} -- placeholder for events
	set mdDate to {} -- placeholder for their dates
	repeat with n from 1 to mCount -- run through the whole calendar
		-- Get [i]summary[/i] and [i]start date[/i] of each
		tell event n of mCal
			set {anEvent, aDate} to {summary of it, start date of it}
			-- Figure in repetitive events by weeks, adjusting forward to today.
			-- This is why we had to review all events - repeats are not listed again.
			if anEvent contains "(" then -- in my Medical calendar only repeats do.
				set paren to offset of "(" in anEvent
				set Rep to character (paren + 1) of anEvent -- assumes less than 10
				-- The next line counts forward from the original to one after today.
				tell (Rep * weeks) to set aDate to today + (it - ((today - aDate) mod it))
			end if
		end tell
		-- Now collect those after today.
		if ((aDate) - today) > 0 then -- dates can only be added or subtracted, not A > B.
			set end of mdEvent to anEvent -- a list of medical appointments coming up
			set end of mdDate to aDate -- a list of dates in the same order as the appointments
		end if
	end repeat
end tell

So, if we had done this and got back the lists mdEvent and mdDate lists, then we could sort them by date like this:

property tLib : (path to scripts folder from user domain) & "Script Library:"
proprty sorter : load script ((Lib as text) & "MultiLists.scpt") as alias
set {mdDate, mdEvent} to sorter's sort2(mdDate, MdEvent) -- done.

More Examples of Our Simple Library Handlers

Let’s use a simpler example to illustrate how to use our MultiLists Library:

-- load it into this script....
property Lib : (path to scripts folder from user domain as text) & "Script Library:"
property LL : load script (Lib & "MultiLists.scpt") as alias
-- And two Lists...
property Lst1 : {15, 7, 5, 2}
property Lst2 : {"Fifteenth", "Seventh", "Fifth", "Second"}

LL's helpMe()
copy {Lst1, Lst2} to {L1, L2}
set double to LL's sort2(L1, L2) -- sorting on the first list
copy Lst2 to RL
set RList to LL's RandList(RL) -- randomizing a list
copy {Lst1, Lst2} to {R1L, R2L}
set r2List to LL's RandList2(R1L, R2L) -- randomizing two
copy Lst2 to OG
set OneGone to LL's removeListItem(2, OG) -- remove an item
copy {Lst1, Lst2} to {TG1, TG2}
set TwoGone to LL's removeListItem2(3, TG1, TG2) -- remove one from two

set blank to return & "------" & return
set msg to "Given the original lists:" & return & makeText(Lst1) & return & makeText(Lst2) & blank & "The double sort returned" & return & makeText(item 1 of double) & return & makeText(item 2 of double) & blank & "The randomized single list returned" & return & makeText(RList) & blank & "The randomized double list is" & return & makeText(item 1 of r2List) & return & makeText(item 2 of r2List) & blank & "With one item in the second list gone:" & return & makeText(OneGone) & blank & "Finally, with an item removed in two" & return & makeText(item 1 of TwoGone) & return & makeText(item 2 of TwoGone) & blank

display dialog msg buttons {"Done"} default button 1

to makeText(aList)
	set text item delimiters to ", "
	set L to aList as text
	set text item delimiters to ""
	return "{" & L & "}"
end makeText

Now, if I could only organize myself suffiently to comb through my scripts, extract families of handlers, and build a huge library of them so I could stop hunting.

The Collection of List Handlers to become ~/Library/Scripts/Script Library/MultiLists.scpt

------ This handler simply returns a text message of instructions.
to helpMe()
	set msg to "MULTILISTS' HELP
This demo Library sorts two lists, removes an
item from a list, randomizes the items in
a list, & randomizes one while keeping a
second properly aligned to the first.
sort2(s1, s2) ASCII (or Date) sorts the  
contents of list s1 while keeping the 
contents of list s2 in register with 
those in list s1.
RandList() accepts a single list as its argument
and returns the list in randomized order.
RandList2 does the same as RandList, but keeps
the two lists in lockstep order: RandList2(L1, L2).
RemoveListItem(N,L) removes the Nth item
from the list L, while RemoveListItem2() uses
RemoveListItem twice in a coordinated manner."
	display dialog msg
end helpMe
-- Sort2 is an adaptation of a script by Kai Edwards
to sort2(s1, s2)
	script M
		property srt : missing value
		property sec : missing value
	end script
	set M's srt to s1
	set M's sec to s2
	tell (count M's srt) to repeat with i from (it - 1) to 1 by -1
		set s to (M's srt)'s item i
		set r to (M's sec)'s item i
		repeat with i from (i + 1) to it
			tell (M's srt)'s item i to if s > it then
				set (M's srt)'s item (i - 1) to it
				set (M's sec)'s item (i - 1) to (M's sec)'s item i
				set (M's srt)'s item (i - 1) to s
				set (M's sec)'s item (i - 1) to r
				exit repeat
			end if
		end repeat
		if it is i and s > (M's srt)'s end then
			set (M's srt)'s item it to s
			set (M's sec)'s item it to r
		end if
	end repeat
	return {M's srt, M's sec}
end sort2
------ This handler randomizes a list, relocating its items in
------ random order. Some may end up in the same position.
to RandList(aList)
	set c to count aList -- so as not to calculate it every loop
	set r to {} -- placeholder for the answer
	repeat until (count r) = c
		tell (some item of aList) to if (it is not in r) then set end of r to it
	end repeat
	return r
end RandList
------ This handler randomizes one list and keeps the second in the
------ same random order as the first.
to RandList2(L1, L2)
	set c to count L1 -- so as not to calculate it in every repeat
	set k to {} -- place holder for index
	set r to {} -- placeholder for randomized List1
	set s to {} -- placeholder for randomized List2
	repeat until (count k) = c
		tell (random number from 1 to c) as integer to if it is not in k then set end of k to it
	end repeat
	repeat with n from 1 to c
		tell item n of k
			set end of r to item it of L1
			set end of s to item it of L2
		end tell
	end repeat
	return {r, s}
end RandList2
-- From a script by Matt Neuburg
-- This handler removes an item (by item number from a list
to removeListItem(anItemNum, aList)
	if aList is {} then
		return {}
	else if anItemNum is 1 then
		return rest of aList
		return {item 1 of aList} & removeListItem(anItemNum - 1, rest of aList)
	end if
end removeListItem
-- This handler removes the same item number from two lists
to removeListItem2(anItemNum, aList, bList)
	set L1 to my removeListItem(anItemNum, aList)
	set L2 to my removeListItem(anItemNum, bList)
	return {L1, L2}
end removeListItem2