I am working on a script to group file names and store them in a property list (.plist) file.
The following function takes in a collective name for the group of files (ie. “Group1”) and a list of files belonging to it (ie. {“File1.png”, “File2.png”, “File3.png”}). It is supposed to write the information to the plist in the following format:
on saveToPlist(groupName, groupFiles)
tell application "System Events"
set theParentDictionary to make new property list item with properties {kind:record}
set thePropertyListFilePath to "~/Desktop/Groups.plist"
-- Get / Create Property List File
if not (exists file thePropertyListFilePath) then
set thePropertyListFile to make new property list file with properties {contents:theParentDictionary, name:thePropertyListFilePath}
log "New plist created"
else
set thePropertyListFile to property list file thePropertyListFilePath
log "Plist exists"
end if
-- Add to Property List File
tell property list items of thePropertyListFile
make new property list item at end with properties {kind:date, name:"lastUpdate", value:current date}
if not (exists property list item "groups") then
set groupsRec to make new property list item at end with properties {kind:record, name:"groups"}
log "groups record created"
else
set groupsRec to property list item "groups"
log "groups record already exists"
end if
make new property list item at end of groupsRec with properties {kind:list, name:groupName, value:groupFiles}
end tell
end tell
end saveToPlist
There is a problem with the last section (adding to the plist file). The script doesn’t seem to find the property list item “groups”. I would also like to like the script to append to the “groups” record instead of replacing the content completely.
How would I access the values in each group?
How would I add/remove individual values in each group’s array once they are already in the file?
Any help is appreciated. Please let me know if you need clarification on anything. Cheers.
Dealing with anything but the simplest property list file in System Events is slow and painful. It’s much simpler and faster to use AppleScriptObjC. That way you can just copy-and-paste the handlers to read and write the file, and manipulate the information as a normal AppleScript record.
Here’s an example:
use AppleScript version "2.5" -- 10.11 or later
use framework "Foundation"
use scripting additions
set thePath to POSIX path of ((path to desktop as string) & "Groups.plist")
-- define your info as a record or array
set theInfo to {lastUpdate:(current date), groups:{Group1:{"File1.png", "File2.png", "File3.png"}, Group2:{"File4.png", "File5.png", "File6.png"}}}
-- store it in a file
my writeToPlist:theInfo inFile:thePath
-- read from file
set newInfo to my readPlistFrom:thePath
-- modify info
set groups of newInfo to groups of newInfo & {Group3:{"File7.png", "File8.png", "File9.png"}}
set lastUpdate of newInfo to (current date)
--write newInfo to property list
my writeToPlist:newInfo inFile:thePath
on writeToPlist:someASThing inFile:posixPath
-- convert to property list data
set {theData, theError} to current application's NSPropertyListSerialization's dataWithPropertyList:someASThing |format|:(current application's NSPropertyListXMLFormat_v1_0) options:0 |error|:(reference)
if theData is missing value then error (theError's localizedDescription() as text)
-- write data to file
set theResult to theData's writeToFile:posixPath atomically:true
return theResult as boolean -- returns false if save failed
end writeToPlist:inFile:
on readPlistFrom:posixPath
-- read file as data
set theData to current application's NSData's dataWithContentsOfFile:posixPath
-- convert to Cocoa object
set {theThing, theError} to current application's NSPropertyListSerialization's propertyListWithData:theData options:0 |format|:(missing value) |error|:(reference)
if theThing is missing value then error (theError's localizedDescription() as text)
-- we don't know the class of theThing for coercion, so...
set listOfThing to current application's NSArray's arrayWithObject:theThing
return item 1 of (listOfThing as list)
end readPlistFrom:
Thank you for the hint with AppleScriptObjC. I see what you are doing with theInfo at the very top, in my quest to make it as dynamic as possible, I have also included the following code to assign a name to the group (instead of calling it Group1…3 etc), and pass in a list.
-- A list
set theList to {"File1", "File2", "File3"}
-- Assign Name to Group
set groupName to the text returned of (display dialog "Name of this group:" default answer "")
I then update using:
...
set newInfo to my readPlistFrom:thePath
set groups of newInfo to groups of newInfo & {groupName:theList} --> Issue described below
my writeToPlist:newInfo inFile:thePath
...
How do I set the name of the group to my input dynamically (ie. the string in groupName - returned from the dialog)? Right now, it saves it as “groupName” instead of the string contained in the variable. (theList saves just fine)
Shane, it now successfully saved the record using the dialog result as key.
When I read back the file using the readPlistFrom routine, I am unable to enumerate the results in a manner that allows me to tell the routine the key and get back a list of values for that key, rather than a dump of everything.
on getValuesForKey:theKey
--> return values at theKey as list
end getValuesForKey:
I would also like to be able to delete a key (and thus any values associated with it) by specifying which key to remove.
on deleteRecord:theKey
--> delete record (key-value(s) pair) at theKey
end deleteRecord:
Lastly but not least, I would like to manipulate a specific value under a specific key, in order to update the record for that key only.
on manipulate:theKey atValue:theValue newValue:newValue
--> update/replace theValue under key theKey with newValue
end manipulate:atValue:newValue:
From a few hours of research, I know about removeObjectForKey but can’t seem to implement it correctly. Does the file need to be read in, key-values removed and saved or is there a simpler way that does it all in one sweep.
This is also probably better done in ASObjC. If you’re comfortable with it, you could modify the second handler so it just returns theThing, which in this case with be an NSDictionary. You can also change the options parameter to (current application’s NSPropertyListMutableContainersAndLeaves) so you can modify it in place.
Anyhow, once you have the groups dictionary, you can do something like this rough outline:
set theKeys to groupsDict's allKeys()
repeat with oneKey in theKeys
-- retrieve value
set itsValue to groupDict's objectForKey:oneKey
-- or to change it
if oneKey as text = "Group1" then
groupDict's setObject:newListOfEntries forKey:oneKey
-- or to delete it
if oneKey as text = "GroupUnwanted" then
groupDict's setObject:(missing value) forKey:oneKey
An example of a hybrid script for writing/reading AppleScript records from me:
use AppleScript version "2.4" -- Yosemite (10.10) or later
use framework "Foundation"
use scripting additions
-- write
do shell script "defaults write my.domain myRecord -dict 'aString' -string 'Hello,word!' 'anInteger' -integer '1' 'aReal' -float '3.14'"
delay 1
-- read back
set plistPath to POSIX path of (path to library folder from user domain) & "Preferences/my.domain.plist"
set plistData to my (NSData's dataWithContentsOfFile:plistPath)
set plist to (my (NSPropertyListSerialization's propertyListWithData:plistData options:0 format:(my NSPropertyListXMLFormat_v1_0) |error|:(missing value)))
set theRecord to (plist's objectForKey:"myRecord") as record
--> {aString:"Hello,word!", aReal:3.140000104904, anInteger:1}