Basic Script Object
Applescript is object-oriented by it's very nature, but objects are limited to the application being worked on or records, which are essentially property-only objects. Script objects allow custom methods and more functionality. Only use Script Object if methods are needed, otherwise always use records.
script scriptName
property property1 : "property1 value"
property property2 : "property2 value"
property property3 : "property3 value"
on handler1(param1, param2, param3)
-- your code goes here
end handler1
on handler2(param1, param2, param3)
-- your code goes here
end handler2
end script
How to use properly
A very important distinction needs to be made about creating script object. Always use a method to that returns a script object because using "copy" to create the object actually causes a "deep copy" that duplicates the child AND parent (in the example, the whole Applescript itself) which will use so much memory and processor cycles as to crash to the Mac.
on MakeScriptObject()
script
property Vegetable : ""
end script
--no return statement is necessary for script objects.
end MakeScript Object
set John to MakeScriptObject() of me
set Vegetable of John to "Swiss chard"
return Vegetable of myScriptObject
--result: "Swiss chard"
OCDictionary Data Storage Class
N.B.: This class is now in active development at my GitHub account as
ASDictionary. While this class works as writ, no more updated will be made here, and the latest version is much faster and easier to use. Really, you should just go there now.About
The record class in Applescript does a lot of the same work as a dictionary, but it isn't dynamic in any way other than changing values for keys. Keys for key-value pairs are hard-coded, keys can only be strings (because they are hard-coded), and there is no way to iterate through the keys (and thus the values) once a record is created. This simple dictionary class gives all of that functionality in native Applescript form.
The caveats
The problem, though, is that because we are looping through a list to find items, things can go slow, even for Applescript. There is a checkDataIntegrity flag that can be turned on and off, and that increases the speed (everything being relative in Applescript) but all data and dictionary integrity checks are switched on by default. What this means is that if you turn off the checking, then you can end up with a dictionary with mismatched key-value pairs because one item may be null and the other not.
Applescript does a lot heavy-lifting for you; you don't get access to specific data comparison methods (either something equals something else or it doesn't), data is loosely-typed (almost too much), and coersion is done for you for the most part. By result, there aren't a lot of tools at our disposal for creating something like this, but there is enough to create a modest yet functioning dictionary.
Also, convenience methods are easy to create as well:
on MakeDictionaryWithValuesAndKeys(someValues, someKeys) -- (list, list) as OCDictionary
set newDictionary to MakeDictionary() of me
tell newDictionary
set valuesAdded to addValuesForKeys(someValues, someKeys)
end tell
return newDictionary
end MakeDictionaryWithValuesAndKeys
-- simply call with:
-- set newDictionary to MakeDictionaryWithValuesAndKeys({"ack", "greeble", "ponies"}, {"ACK", "GREEBLE", "PONIES"}) of me
Possible future plans
Right now, the accessor methods only go by the key and not the value, but that can easily be added. I would like to see more of the functionality that I get in REALbasic and Cocoa's NSDictionary class.
I would like to see this put aside and have access to the NSDictionary class within Cocoa with all of its features, but that would be an add-on and this, at least, does away with the dependencies (and land mines) that go along with that.
Documentation
Introduction
Essentially, the class is a wrapper for a private list of records with the format {key:data, value:data}. When a key is accessed, it is essentially going down the list looking for the first record with the given key and acting accordingly based on whether it finds one or not.
Because it is iterating through a list, using this class can get expensive with large amounts of data. There is some data checking that can be toggled on and off, but Applescript is generally slow to begin with so there isn't too much that can be done about it. But, as I see it, the benefits in functionality outweigh any speed issues. Otherwise, this is light, flexible, and designed to be extensible when need be.
This uses the Script Object features of Applescript, which is essentially allows custom OOP in Applescript complete with inheritance. The caveat is that doing this can be ''very'' resource and memory intensive. If you aren't familiar with Script Objects, then I highly suggest you read the Applescript Language Guide, but essentially all of the action, the class in of itself, is held within the script OCDictionary...end script block within the MakeDictionary() subroutine.
run{}
This subroutine offers examples of syntax and functionality. This combined with the MakeDictionary() subroutine makes for a fully working script.
MakeDictionary() -- as OCDictionary
If Script Objects are to be used, they need to be declared and returned, and that is all this subroutine does. This contains the entire OCDictionary declaration, so just copy and paste into your script and call with the simple
set testDictionary to MakeDictionary() of mehasKey(aKey) -- (object) as boolean
Simple function that will return whether a key exists or not.
toggleDataIntegrityChecks() -- as boolean
This toggles the data integrity checks. Data integrity is checked on keys and values being sent to the class. That check is very simple—only checking for null or empty list values—so it's very fast, but when compiled over time, it can add up.
getKeys() -- as list
Returns a list of all the keys found in all the records. If there are no records, it will return an empty list
setValueForKey(aValue, aKey) -- (object, object) as boolean
False can be returned if data integrity checking is enabled and either the key or the value is invalid. Otherwise, this always returns true because of a key doesn't exist, it will create a new key-value pair.
valueForKey(aKey) -- (object) as object or (kOCDictionary_ValueNotFound as string)
This returns the given value for a key, regardless if that value is null or not. If it cannot find a value for that key because the key does not exist, it returns the internal error "kOCDictionary_ValueNotFound".
addValuesForKeys(someValues, someKeys) -- (list, list) as boolean
This allows the addition of multiple keys and values as lists. If data integrity checking is on, then the subroutine checks to make sure there are no empty lists and that they have a one-to-one relationship with each other (i.e., both list lengths are the same. The order of both is entirely up to you). Any error along those lines returns false and nothing is added to the dictionary. If data integrity is off, the lists are added "as is" and can result in null values in the dictionary.
dictionaryIntegrityCheck(verboseFlag) -- (boolean) as boolean
This is added as a convenience method to check is any values are either null or have empty lists. The verbose flag will send basic information about key-value pairs to the Applescript log when errors are found.
The Code
on run {}
set testDictionary to MakeDictionary() of me
tell testDictionary
(* Basic Operations *)
(* Add a value and Key *)
log "setValueForKey(oop, OOP)"
set valueForKeySet to setValueForKey("oop", "OOP")
log valueForKeySet
(* Add a list of values and keys *)
log "addValuesForKeys({ack, greeble, ponies}, {ACK, GREEBLE, PONIES})"
set valuesAddedForKeys to addValuesForKeys({"ack", "greeble", "ponies"}, {"ACK", "GREEBLE", "PONIES"})
log valuesAddedForKeys
(* Get and set values for keys *)
log "setValueForKey(\"Luc Teyssier\", OOP)"
set valueSetForKey to setValueForKey("Luc Teyssier", "OOP")
log valueSetForKey
log "set myValueForKey to valueForKey(OOP)"
set myValueForKey to valueForKey("OOP")
log myValueForKey
(* Get all of the keys and iterate through the pairs *)
log "set theKeys to getKeys()"
set theKeys to getKeys()
log theKeys
log "iterate through all keys"
set lastKey to (count theKeys)
repeat with k from 1 to lastKey
set theKey to item k of theKeys
set theValue to valueForKey(theKey)
log {theKey, theValue}
end repeat
(* Operations That Will Cause Errors *)
log "toggleDataIntegrityChecks()"
log toggleDataIntegrityChecks()
log "setValueForKey(emptyValueList, emptyKeyList)"
log setValueForKey({}, {})
-- nothing should be added to keys or values,
-- but since we turned off data integrity checks,
-- we get it added but the report catches it
log "addValuesForKeys(unmatchedValueList, unmatchedKeyList)"
log addValuesForKeys({"Kate", "Charlie"}, {"Jean-Paul Cardon", "Bob", "Juliette", "Concierge"})
-- we should see errors in the log and nothing added
log "keyFound to hasKey(supercalifrajilisticexpialidocious)"
set keyFound to hasKey("supercalifrajilisticexpialidocious")
log keyFound
-- we should get back a false here
log "set theValueForKey to valueForKey(supercalifrajilisticexpialidocious)"
set theValueForKey to valueForKey("supercalifrajilisticexpialidocious")
log theValueForKey
-- we should get back the kOCDictionary_ValueNotFound error message here
(* Check to make sure our data is clean so we don't mess up operations later *)
log "set dictionaryIsSafe to dictionaryIntegrityCheck(true)"
set dictionaryIsSafe to dictionaryIntegrityCheck(true)
log dictionaryIsSafe
end tell
end run
on MakeDictionary() -- as OCDictionary
script OCDictionary
(* Public properties *)
property kOCDictionary_ValueNotFound : "kOCDictionary_ValueNotFound"
(* Private properties *)
property __keyValuePairs : {}
property __kOCDictionary_NoStoredKeys : -1
property __kOCDictionary_KeyNotFound : -2
property __kOCDictionary_InvalidKey : -3
property __kKeyIndexErrors : {__kOCDictionary_NoStoredKeys, __kOCDictionary_KeyNotFound, __kOCDictionary_InvalidKey}
property __checkDataIntegrity : true
(* Public SubRoutines *)
to hasKey(aKey) -- (object) as boolean
set keyValueIndex to __indexOfKey(aKey) of me
if keyValueIndex is in __kKeyIndexErrors then
return false
end if
return true
end hasKey
to toggleDataIntegrityChecks() -- as boolean
if __checkDataIntegrity = true then
set __checkDataIntegrity to false
else
set __checkDataIntegrity to true
end if
return __checkDataIntegrity
end toggleDataIntegrityChecks
to getKeys() -- as list
set keyList to {}
set keyValuePairCount to (count __keyValuePairs)
if keyValuePairCount = 0 then
return keyList
end if
repeat with thisKeyValuePair from 1 to keyValuePairCount
set theKeyValuePair to item thisKeyValuePair of __keyValuePairs
set theKey to key of theKeyValuePair
set end of keyList to theKey
end repeat
return keyList
end getKeys
to setValueForKey(aValue, aKey) -- (object, object) as boolean
if __checkDataIntegrity then
set aValuePassed to __dataIntegrityCheck(aValue)
set aKeyPassed to __dataIntegrityCheck(aKey)
if not (aValuePassed) or not aKeyPassed then return false
end if
set keyValueIndex to __indexOfKey(aKey) of me
if keyValueIndex is in {__kOCDictionary_NoStoredKeys, __kOCDictionary_KeyNotFound} then
set newKeyValuePair to __makeKeyValuePairWithKeyAndValue(aKey, aValue) of me
set end of __keyValuePairs to newKeyValuePair
else
set theKeyValuePair to item keyValueIndex of __keyValuePairs
set value of theKeyValuePair to aValue
end if
return true
end setValueForKey
to valueForKey(aKey) -- (object) as object or (kOCDictionary_ValueNotFound as string)
set keyValueIndex to __indexOfKey(aKey) of me
if keyValueIndex is in __kKeyIndexErrors then
return kOCDictionary_ValueNotFound
end if
set theKeyValuePair to item keyValueIndex of __keyValuePairs
set theValue to value of theKeyValuePair
return theValue
end valueForKey
to addValuesForKeys(someValues, someKeys) -- (list, list) -- as boolean
set keysCount to (count someKeys)
set valuesCount to (count someValues)
if __checkDataIntegrity then
if keysCount ≠ valuesCount then return false
if keysCount = 0 and valuesCount ≠ 0 then return false
if keysCount ≠ 0 and valuesCount = 0 then return false
end if
set keysCount to (count someKeys)
repeat with thisKey from 1 to keysCount
try
set theKey to item thisKey of someKeys
set theValue to item thisKey of someValues
set newKeyValuePair to __makeKeyValuePairWithKeyAndValue(theKey, theValue) of me
set end of __keyValuePairs to newKeyValuePair
on error
-- fail silently
end try
end repeat
return true
end addValuesForKeys
to dictionaryIntegrityCheck(verboseFlag) -- (boolean) as boolean
set dictionaryIsClean to true
set keyValuePairCount to (count __keyValuePairs)
if keyValuePairCount = 0 then return (dictionaryIsClean = true)
repeat with thisKeyValuePair from 1 to keyValuePairCount
set theKeyValuePair to item thisKeyValuePair of __keyValuePairs
set theKey to key of theKeyValuePair
set theValue to value of theKeyValuePair
set theKeyPassed to __dataIntegrityCheck(theKey)
set theValuePassed to __dataIntegrityCheck(theValue)
if not theKeyPassed or not theValuePassed then
set dictionaryIsClean to false
if verboseFlag then
set recordErrors to {}
set end of recordErrors to "__keyValuePair(" & thisKeyValuePair & ")"
set end of recordErrors to theKeyValuePair
if not theKeyPassed then
set end of recordErrors to "key is null or is an empty list"
end if
if not theValuePassed then
set end of recordErrors to "value is null or is an empty list"
end if
log recordErrors
end if
end if
end repeat
return dictionaryIsClean
end dictionaryIntegrityCheck
(*
Private Subroutines
All error checking is done before we get to these methods, so these should not be called directly.
*)
to __makeKeyValuePairWithKeyAndValue(aKey, aValue) -- (object, object) as record
(* Factory method for key-value pair records *)
set keyValuePair to {key:aKey, value:aValue}
return keyValuePair
end __makeKeyValuePairWithKeyAndValue
to __indexOfKey(aKey) -- (object) as integer
(* This subroutine combined with the __keyValuePairs property is really the crux of the whole class *)
if aKey = null then return __kOCDictionary_InvalidKey
set keyValuePairCount to (count __keyValuePairs)
if keyValuePairCount = 0 then
return __kOCDictionary_NoStoredKeys
else
repeat with thisKeyValuePair from 1 to keyValuePairCount
set theKeyValuePair to item thisKeyValuePair of __keyValuePairs
set theKey to key of theKeyValuePair
if aKey = theKey then
return thisKeyValuePair
end if
end repeat
end if
return __kOCDictionary_KeyNotFound
end __indexOfKey
to __dataIntegrityCheck(newData) -- (object) as boolean
(* This offers only very basic checks: null or empty lists *)
if newData = null then
return false
end if
try
set itemCount to (count newData)
if itemCount = 0 then
return false
end if
on error
-- fail silently
end try
return true
end __dataIntegrityCheck
end script
return OCDictionary
end MakeDictionary