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 me

hasKey(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