Mavericks SmartRecord

This is an ASObjC-based library for a SmartRecord object. AppleScript records have several limitations: you can’t get the keys/labels as strings, you can’t delete items from a record, and you can’t coerce them to text. Unfortunately the scripting bridge ASObjC relies on has a limitation too: it drops entries whose value is missing value. The SmartRecord will return an error if you pass it a record with a value set to missing value (or if one of the labels is an AppleScript keyword).

use framework "Foundation"

script BaseObject -- the parent script object, required for AppleScriptObjC inheritance
end script

on smartRecordWith:aRecord -- call to make a new smartRecord
	script SmartRecord
		property parent : BaseObject -- for inheritance
		property dictStore : missing value -- where the dictionary is stored
		
		on objectForKey:theKey -- returns object for the key, or missing value if not found
			set theResult to dictStore's objectForKey:theKey
			-- if its an array, coerce to a list
			if ((theResult's isKindOfClass:(current application's NSArray)) as integer = 1) then
				return theResult as list
			else -- coerce to list and return item 1; workaround to coerce item of unknown class
				set theResult to theResult as list
				return item 1 of theResult
			end if
		end objectForKey:
		
		on allKeys() -- returns list of keys
			return dictStore's allKeys() as list
		end allKeys
		
		on allValues() -- returns list of values
			return dictStore's allValues() as list
		end allValues
		
		on allKeysForObject:theValue -- return all keys with the given value
			return (dictStore's allKeysForObject:theValue) as list
		end allKeysForObject:
		
		on setObject:theValue forKey:theKey -- modify or add value for key
			dictStore's setObject:theValue forKey:theKey
		end setObject:forKey:
		
		on setObjects:theValues forKeys:theKeys -- modify or add values for keys
			set newDict to current application's NSDictionary's dictionaryWithObjects:theValues forKeys:theKeys
			dictStore's addEntriesFromDictionary:newDict
		end setObjects:forKeys:
		
		on objectsForKeys:theKeys notFoundMarker:theMarker -- return values for keys, and use marker where not found
			return (dictStore's objectsForKeys:theKeys notFoundMarker:theMarker) as list
		end objectsForKeys:notFoundMarker:
		
		on keysSortedByValue() -- all keys, in order based on their values
			return (dictStore's keysSortedByValueUsingSelector:"compare:") as list
		end keysSortedByValue
		
		on removeObjectForKey:theKey -- delete entry
			dictStore's removeObjectForKey:theKey
		end removeObjectForKey:
		
		on removeObjectsForKeys:theKey -- delete entries
			dictStore's removeObjectsForKeys:theKey
		end removeObjectsForKeys:
		
		on addEntriesFromDictionary:newDict -- add dictionary
			dictStore's addEntriesFromDictionary:newDict
		end addEntriesFromDictionary:
		
		on asDictionary() -- return as a dictionary for further use
			return dictStore
		end asDictionary
		
		on asRecord() -- return as a normal record
			return dictStore as record
		end asRecord
		
		on asText() -- return as text
			set theKeys to dictStore's allKeys() as list
			set theString to current application's NSMutableString's |string|()
			repeat with i from 1 to count of theKeys
				(theString's appendString:(current application's NSString's stringWithFormat_(", %@:%@", item i of theKeys, dictStore's objectForKey:(item i of theKeys))))
			end repeat
			return "{" & ((theString's substringFromIndex:2) as text) & "}"
		end asText
		
	end script
	-- set up the initial script object
	if class of aRecord is record then
		set theDict to current application's NSMutableDictionary's dictionaryWithDictionary:aRecord
		-- bug means conversion drops items where value is set to missing value, so we check the number before and after
		if theDict's |count|() as integer is not length of (aRecord as list) then error "Record cannot be converted (contains missing value?)" number -10000
		set dictStore of SmartRecord to theDict
	else -- empty record aka list
		set dictStore of SmartRecord to current application's NSMutableDictionary's dictionary()
	end if
	return SmartRecord
end smartRecordWith:

In use:

use theLib : script "<name of lib>" 

set theDict to theLib's smartRecordWith:{firstName:"Frieda", lastName:"Rhode"}
log theDict's asText()
theDict's setObjects:{"Katrine", 32} forKeys:{"firstName", "age"}
log theDict's asText()
log theDict's objectsForKeys:{"firstName", "address"} notFoundMarker:"not found"
log (theDict's allKeysForObject:32)
log theDict's allKeys()
log theDict's allValues()
theDict's removeObjectForKey:"lastName"
log theDict's asText()

:cool: I think a lot of scripters were waiting for this!