Yo, I wrote this application called “FindReplace” and here is the code:
(*
This application is a droplet for batch-processing text files. You can also drag folders onto it, and it will process
the items inside.
(IMPORTANT: It does not check first if the files are text files -- technically, it will process ANY item that is not a
disk, application, or folder. But it includes an option allowing you to only process specific file types, or to
ignore specific file types).
What it's intended to do: Look inside each file, find strings of text the User specifies, and replace them with
strings of text the User specifies, creating a new file that includes the changes. The original file is kept the same.
The User is allowed to specify multiple find/replace combinations in a single process. Example:
"The Man" is replaced with "The Woman",
"Elephant" is replaced with "Rhinoceros", and
"Disneyland" is replaced with "Knott's Berry Farm", all done at once.
*)
--These variables are global simply because it's a pain to pass them all as arguments to functions.
property exclusionChoice : ""
property exclusionChoices : {}
property extList : {}
property choiceList : {}
property choiceNums : {}
property searchTexts : {}
property replaceTexts : {}
property cOrSs : {}
property caseBools : {}
-- SECTION 1: the handler that runs when you drag items onto the app icon:
on open draggedItems
mainRoutine(draggedItems)
end open --application quits.
-- SECTION 2: the handler that runs when you double-click the app:
on run
set theResult to display dialog "Do you want to select Files or Folders?" buttons ¬
{"Cancel", "Folders", "Files"} default button 3
tell application "Finder"
--Make a dialog window open and ask user to select item(s) to process.
if button returned of theResult is "Folders" then
set chosenItems to choose folder with prompt ¬
"Choose Folder(s) to process" default location desktop as alias with multiple selections allowed
else if button returned of theResult is "Files" then -- if button returned is "File"
set chosenItems to choose file with prompt "Choose File(s) to process" default location ¬
desktop as alias with multiple selections allowed
end if
end tell
mainRoutine(chosenItems)
end run --application quits.
-- SECTION 3: Define functions.
on mainRoutine(itemsToProcess)
set useMostRecent to (choose from list {"Yes", "No"} with title ¬
"Use Most Recent Settings?" with prompt "Choose Yes or No:" OK button name "Proceed") as text
if useMostRecent is "false" then return
if useMostRecent is "No" then
--Note that all even-numbered items in list are case-sensitive options:
set choiceList to {"Specific", "Specific (case-sensitive)", "Begin(anything)End", ¬
"Begin(anything)End (case-sensitive)"}
repeat --until User reviews and approves his specified settings.
--Create arrays to store user settings for each inner loop iteration:
set {choiceNums, searchTexts, replaceTexts, cOrSs, caseBools} to ¬
{{}, {}, {}, {}, {}}
--Inner loop
repeat -- until User chooses to not add more find/replace pairs.
set listChoice to (choose from list choiceList ¬
with title "”””” What Shall I Do Sir? ””””" with prompt ¬
"Click an option:" OK button name "Proceed") as text
--If user hits 'cancel' when choosing from list it returns false as string, not boolean.
if listChoice is "false" then return
set end of choiceNums to getIndex(listChoice, choiceList) as integer
--Get current choiceNum from choiceNums:
set choiceNum to LI(-1, choiceNums)
--All even choiceNums will be case-sensitive searches:
if (choiceNum mod 2 = 0) then
set end of caseBools to true
else
set end of caseBools to false
end if
--Assign the default context setting:
set end of cOrSs to "All"
if choiceNum < 3 then
set end of searchTexts to text returned of ¬
(display dialog "Enter the text you want to find:" default answer "")
--Deal specifically with the context choice:
set cOrSList to {"” where it is Contained inside a longer word", ¬
"” where it is Separate from other words, ", "” in All instances"}
set cOrSchoice to ¬
(choose from list cOrSList with title "”””” In What Context? ””””" with prompt ¬
"Replace the total search text ”" OK button name "Proceed") as text
if cOrSchoice is "false" then return
set cOrSNum to getIndex(cOrSchoice, cOrSList) as integer
if cOrSNum = 1 then
setLI(-1, cOrSs, "Contained")
else if cOrSNum = 2 then
setLI(-1, cOrSs, "Separate")
end if
else -- If listChoice is not item 1 or 2:
--If user chose Begin(anything)End you don't give them the context choice:
display dialog "Enter the beginning of the text to be replaced:" default answer ""
set begin_search_string to (text returned of result)
display dialog "Enter the ending of the text to be replaced:" default answer ""
set end_search_string to (text returned of result)
set end of searchTexts to {begin_search_string, end_search_string}
end if
display dialog "Enter the text to replace it all with:" default answer ""
set end of replaceTexts to (text returned of result)
display dialog "Would you like to do more after that?" buttons {"Cancel", "Yes", "No"} ¬
default button 3
if button returned of result is "No" then exit repeat
end repeat
set exclusionChoices to {"Process all file types except.... ", ¬
"Skip all file types except.... ", "Skip this option"}
set exclusionChoice to (choose from list exclusionChoices with title ¬
"Any file types to exclude, or focus on?" with prompt ¬
"Click an option:" OK button name "Proceed") as text
if exclusionChoice is not LI(3, exclusionChoices) then
if exclusionChoice is LI(1, exclusionChoices) then
set extList to text returned of (display dialog ¬
"Enter name extensions of files you want to skip" & return & ¬
"(separate each with commas):" default answer "")
else if exclusionChoice is LI(2, exclusionChoices) then
set extList to text returned of (display dialog ¬
"Enter name extensions of files you want to process" & return & ¬
"(separate each with commas). All other types will be skipped:" default answer "")
end if
--Remove periods from extList if user added any:
set extList to replace(".", "", extList)
--Separate the different extensions into a list of items, using either a comma or space
--as the separator:
set extList to explode(extList, {",", " "})
set extList to removeEmptyStrings(extList) -- in case there are any empty strings.
else
set extList to {}
end if
--Display a 'summary of settings' dialog:
if userApprovesSettings() then exit repeat
end repeat
end if
set theLists to {choiceNums, searchTexts, replaceTexts, cOrSs, caseBools}
--This condition check is only for debugging:
if countCheck(theLists) is true then
formatSearchAndReplaceTexts()
--Begin the loop that processes each item:
repeat with i from 1 to (count of itemsToProcess)
processItem(LI(i, itemsToProcess))
end repeat -- Item processing ends.
display dialog "Finished." buttons {"OK"} default button "OK" giving up after 3
--Application quits.
else --for debugging:
return theLists
end if
end mainRoutine
on processItem(theItem)
--Check what kind of item the draggedItem is:
set theItem to (theItem as text)
tell application "System Events"
set theProperties to properties of (theItem as alias)
set {theKind, itemID, nameExt} to {kind of theProperties, ¬
name of theProperties, name extension of theProperties}
end tell
--Protect the "versions" folders so the contents don't get modified:
if (theKind is "Folder" and itemID does not contain "versions") then
processFolder(theItem)
else if (theKind is not in {"Volume", "Application", "Folder"}) then
if (exclusionChoice is LI(1, exclusionChoices) and nameExt is not in extList) ¬
or (exclusionChoice is LI(2, exclusionChoices) and nameExt is in extList) or ¬
(exclusionChoice is LI(3, exclusionChoices)) then
--process the file once for every item in choiceNums:
repeat with i from 1 to (count of choiceNums)
processFile(theItem, LI(i, choiceNums), LI(i, searchTexts), LI(i, replaceTexts), ¬
LI(i, cOrSs), LI(i, caseBools))
end repeat
end if
end if
end processItem
--theFolder must be an alias. This is a recursive function that will call itself
--if it finds a folder inside of theFolder.
on processFolder(theFolder)
tell application "System Events"
set everyItem to path of every item of (theFolder as alias) whose visible is true
end tell
repeat with i from 1 to (count of everyItem)
set theItem to (item i of everyItem) as text
processItem(theItem)
end repeat
end processFolder
on processFile(theFile, choiceNum, searchText, replaceText, cOrS, caseBool)
--Replace the old text with the new and write it to a new file in the same location:
set theResult to findReplaceToNewFile(choiceNum, searchText, replaceText, theFile, ¬
cOrS, caseBool)
if theResult is not false then --Then the modifying of the text is now done.
-- Move the original file into the 'versions' folder:
makeFolderAndMoveOriginalFile(theFile, theResult)
end if
end processFile
(*
findReplaceToNewFile() uses perl inside shell scripts to perform the find&replace
and write the changes to a new file. theFile must be the file's entire path as a string.
searchStrings can be either a string or list of strings. Returns false if error occurs and no new file is made.
If not false, it returns the most recent modification date of theFile, which
is used by processFile() to rename theFile with the date&time prepended to it.
*)
on findReplaceToNewFile(choiceNum, searchString, replaceString, theFile, cOrS, caseBool)
try
set {fileName, uniqueString, newFile, shellString} to ¬
{itemName(theFile), "¢§-_0_¢R0çç--§§Ã¥¢", (theFile & ".new"), ""}
--use system events to get last-modified property from theFile:
tell application "System Events" to set modDate to (modification date of item theFile)
--Make a copy of theFile with the extension ".new" .
--Make new copy of newFile with return chars replaced with uniqueString.
set shellList to {copyItem(theFile, newFile, false), (perlSubstitution("\n", uniqueString, ¬
newFile, newFile & ".new", caseBool, false) & ¬
renameItem(newFile & ".new", fileName & ".new", false))}
repeat with i from 1 to (count of shellList)
set shellString to (shellString & LI(i, shellList))
end repeat
do shell script shellString
--Check if searchString contains newline chars. If so, replace with uniqueString:
if (searchString contains "\r") or (searchString contains "\n") then
set searchString to replace({"\r", "\n"}, uniqueString, searchString)
end if
--Replace searchString with replaceString and place changes in new file.
--Replace uniqueString with newline chars in newFile.
do shell script (perlSubstitution(searchString, replaceString, newFile, newFile & ".new", ¬
caseBool, false) & perlSubstitution(uniqueString, "\n", newFile & ".new", newFile, ¬
caseBool, false) & deleteItem(newFile & ".new", false))
on error
return false
end try
return modDate
end findReplaceToNewFile
--Formats and escapes each item in searchTexts into a regex pattern based on user's choiceNumber.
--Escapes special characters in each item in replaceTexts.
on formatSearchAndReplaceTexts()
repeat with i from 1 to (count of searchTexts)
--Format searchText into a regex pattern based on user's choice number:
setLI(i, searchTexts, formattedSearchString(LI(i, choiceNums), LI(i, searchTexts), LI(i, cOrSs)))
--Escape special characters in replaceText:
setLI(i, replaceTexts, escapedStrings(LI(i, replaceTexts)))
end repeat
end formatSearchAndReplaceTexts
-- This function decides which find&replace pattern to use, based on user's choice number.
--Returns the string pattern. containedOrSeparate is the option of whether
--to replace searchString only in instances where it's a separate word, only
--in instances where it's contained inside a larger word, or all instances. Values can be "Contained",
--"Separate", or "All".
on formattedSearchString(choiceNum, searchStrings, containedOrSeparate)
if choiceNum = 0 then return false
if containedOrSeparate is "All" then
set cOrS to {"", ""}
else if containedOrSeparate is "Separate" then
--Use negative look-behind and negative look-ahead to
--make sure there's no alphanumeric character just before
--or after the searchString:
set cOrS to {"(?<![a-zA-Z0-9])", "(?![a-zA-Z0-9])"}
end if
--Escape the strings in case they contain special characters:
set searchStrings to escapedStrings(searchStrings)
if (choiceNum < 3) then
if (containedOrSeparate is "Contained") then return ¬
containedPattern(searchStrings as text) --function stops.
--Else, it's either "Separate" or "All", and the code is same for both:
return (LI(1, cOrS) & (searchStrings as text) & LI(2, cOrS))
end if
--|begin(middle)end| find&replacing doesn't include the containedOrSeparate
--option, since the |begin(middle)end| searchString can end up very long, and
--it's unlikely you would ever need the precise control those options give you.
--Using those options for this type of find&replace method can also
--cause you to end up with unexpected results which are hard to control.
if (choiceNum < 5) then return (LI(1, searchStrings) & "(.*?)" & LI(2, searchStrings))
--returns a |begin(middle)end|Replace pattern.
if (choiceNum < 8) then
if (choiceNum = 5) then return #Add a string pattern here
if (choiceNum = 6) then return #Add a string pattern here
if (choiceNum = 7) then return #Add a string pattern here
end if
end formattedSearchString
--This returns a regex pattern containing the user-submitted searchString,
--and is needed when user wants to replace only instances of searchString
--that are contained inside longer words.
on containedPattern(searchString)
set {negLkBehind, negLkAhead} to {"(?<![a-zA-Z0-9])", "(?![a-zA-Z0-9])"}
set {posLkBehind, posLkAhead} to {"(?<=[a-zA-Z0-9])", "(?=[a-zA-Z0-9])"}
return "((" & posLkBehind & "|" & negLkBehind & ")" & (searchString) & posLkAhead & "|" & ¬
posLkBehind & (searchString) & "(" & posLkAhead & "|" & negLkAhead & "))"
end containedPattern
--Escape any special characters that might be in searchStrings. Returns a list of strings
--containing escaped characters.
on escapedStrings(searchStrings)
set {specialChars, theList} to {{"$", "^", "*", "(", ")", "?", "+", "[", "|", ".", "/"}, {}}
--If searchStrings is just a literal string then convert it to a list item:
if class of searchStrings is text then
set end of theList to searchStrings
set searchStrings to theList
end if
--If the user includes backslashes in the searchStrings, applescript automatically
--escapes them for us. But they need to be escaped again for the program to work,
--meaning they're quadrupled in total:
repeat with n from 1 to (count of searchStrings)
setLI(n, searchStrings, replace("\\", "\\\\", LI(n, searchStrings)))
end repeat
--Create the shell string that will perform escaping:
repeat with n from 1 to (count of searchStrings)
set escapeScript to "echo " & quoted form of (LI(n, searchStrings) as text) & " | sed -E "
--Append a command to deal specially with escaping the backslash character:
set escapeScript to (escapeScript & "-e 's#\\\\#\\\\\\\\#g' ")
--Use a loop to create the rest of the shell script:
repeat with i from 1 to (count of specialChars)
set escapeScript to (escapeScript & "-e 's#\\" & LI(i, specialChars)) ¬
& "#\\\\" & LI(i, specialChars) & "#g' "
end repeat
--Run the shell and put resulting string inside searchStrings:
setLI(n, searchStrings, do shell script escapeScript)
end repeat
return searchStrings
end escapedStrings
--This function returns a specially formatted date-time string, i.e, "140211-201412",
--named after the current date and time.
on currentDateTime()
set curDate to (current date)
set shortString to short date string of curDate
set theList to explode(shortString, "/")
if (count of (LI(1, theList))) = 1 then
setLI(1, theList, ("0" & (item 1 of theList)))
end if
if (count of (item 2 of theList)) = 1 then
setLI(2, theList, ("0" & (item 2 of theList)))
end if
set theDate to (item 3 of theList) & (item 1 of theList) & (item 2 of theList)
set AppleScript's text item delimiters to ""
set theTime to time of curDate
set theHour to (round (theTime / 3600) rounding down) as string
set hourInt to theHour as integer
set hourSecs to (hourInt * 3600)
set minSecs to ((theTime - hourSecs) / 60)
set theMin to (round minSecs rounding down) as string
set minInt to theMin as integer
set minSecs to (minInt * 60)
set theSec to (theTime - hourSecs - minSecs) as string
if ((count of theHour) = 1) then set theHour to ("0" & theHour)
if ((count of theMin) = 1) then set theMin to ("0" & theMin)
if ((count of theSec) = 1) then set theSec to ("0" & theSec)
return (theDate & "-" & theHour & theMin & theSec)
end currentDateTime
on getIndex(theItem, theList)
if class of theList is not in {integer, real, text, list} then return false --function stops.
--If theList is a number then coerce into text:
if (count of theList) is 0 then set theList to (theList as text)
if theItem is not in theList then return false -- function stops.
--Else, theItem must be in theList, so:
set indexList to {}
set itemLength to (count of (theItem as text))
if (count of theList) is 1 then -- Then theItem IS theList.
set end of indexList to 1
return indexList -- function stops.
end if
if class of theList is list then
repeat with i from 1 to count of theList
if (theItem is (LI(i, theList))) then set end of indexList to i -- Appends number to end of list.
end repeat
else if class of theList is text then -- Then theItem is also text.
set {theLimit, x, i} to {count of theList, 1, 1}
set theItem to (theItem as text)
repeat while theLimit > (itemLength - 1)
if theItem is (characters i thru (i + itemLength - 1) of theList as text) then
set end of indexList to i
end if
set i to (i + 1)
set theLimit to (theLimit - 1)
end repeat
end if
if indexList is {} then return false
--Else:
return indexList
end getIndex
--Replaces searchString with replaceString inside theString:
on replace(searchString, replaceString, theString)
set item_list to explode(theString, searchString)
set theResult to implode(item_list, replaceString)
return theResult -- returns a new, modified string.
end replace
-- This function separates pieces of a string into list items, using theDelimit
-- as the separator. theDelimit can be either string or list of strings.
on explode(theString, theDelimit)
set origDelimit to AppleScript's text item delimiters
set AppleScript's text item delimiters to theDelimit
set theResult to every text item of theString
set AppleScript's text item delimiters to origDelimit
return theResult
end explode
--This function re-assembles a list of strings into a single string,
--using theDelimit as glue to reconnect each string. theDelimit must be a string.
on implode(textlist, theDelimit)
set origDelimit to AppleScript's text item delimiters
set AppleScript's text item delimiters to theDelimit
set theString to (textlist as string)
set AppleScript's text item delimiters to origDelimit
return theString
end implode
--This function is just for creating a short-hand way of accessing a list item.
--ItemNum can be a single integer, or a list of two integers for accessing a range of items:
on LI(itemNum, theList)
if class of itemNum is integer then
return (item itemNum of theList)
else if class of itemNum is list then
return (items (item 1 of itemNum as integer) thru ¬
(item 2 of itemNum as integer) of theList)
end if
end LI
--This function is for assigning a value to a list item:
on setLI(itemNum, theList, theValue)
set item itemNum of theList to theValue
end setLI
--Removes items that are empty strings from theList.
on removeEmptyStrings(theList)
set newList to {}
repeat with i from 1 to (count of theList)
if LI(i, theList) is not "" then
set end of newList to LI(i, theList)
end if
end repeat
return newList
end removeEmptyStrings
--theItem must be a colon-delimited path string.
--Example: "Macintosh HD:Users:Username:Desktop:Filename.txt"
--Returns theItem's name (without the full path) as string.
on itemName(theItem)
set theParts to explode(theItem, ":")
if theParts ends with "" then set theParts to LI({1, -2}, theParts)
return LI(count of theParts, theParts) as text
end itemName
--Returns a shell string changing the working directory to the one
--containing theItem. theItem must be a colon-delimited path string.
--Example: "Macintosh HD:Users:Username:Desktop:Filename.txt"
on itemLocation(theItem)
set theItem to POSIX path of theItem
if theItem ends with "/" then set theItem to (characters 1 thru -2 of theItem)
set theParts to explode(theItem, "/")
set theDirectory to (implode(LI({1, -2}, theParts), "/"))
return "cd " & quoted form of theDirectory & " \n "
end itemLocation
--theItem and newLocationName must both be colon-delimited path strings.
on moveItem(theItem, newLocationName, runBool)
set newLocationName to POSIX path of newLocationName
if newLocationName ends with "/" then ¬
set newLocationName to (characters 1 thru -2 of newLocationName)
set theName to itemName(theItem)
set shellString to (itemLocation(theItem) & "mv " & (quoted form of theName) & ¬
" " & (quoted form of (newLocationName)) & " \n ")
if runBool is false then return shellString
do shell script shellString
end moveItem
--theItem must be colon-delimited path string.
on deleteItem(theItem, runBool)
set theItem to POSIX path of theItem
set shellString to ("rm " & (quoted form of theItem) & " \n ")
if runBool is false then return shellString
do shell script shellString
end deleteItem
--theItem and newLocationName must both be colon-delimited path strings.
on copyItem(theItem, newLocationName, runBool)
set theName to itemName(theItem)
set theItem to POSIX path of theItem
set newLocationName to POSIX path of newLocationName
set shellString to ("cp " & (quoted form of theItem) & ¬
" " & (quoted form of newLocationName) & " \n ")
if runBool is false then return shellString
do shell script shellString
end copyItem
--theItem must be colon-delimited path to theItem. newName is just the name without the path.
on renameItem(theItem, newName, runBool)
set theName to itemName(theItem)
set shellString to (itemLocation(theItem) & "mv " & (quoted form of theName) & " " & ¬
(quoted form of newName) & " \n ")
if runBool is false then return shellString
do shell script shellString
end renameItem
--newFolderPath must be entire string path.
on makeFolder(newFolderPath)
set newFolderPath to POSIX path of newFolderPath
do shell script "mkdir " & (quoted form of newFolderPath)
end makeFolder
on perlSubstitution(searchString, replaceString, oldFile, newFile, caseBool, runBool)
set {oldFileName, newFileName, perlCase} to {itemName(oldFile), itemName(newFile), "i"}
if caseBool is true then set perlCase to ""
set shellString to itemLocation(oldFile) & "perl -pe " & (quoted form of ("s/" & ¬
searchString & "/" & replaceString & "/" & perlCase & "g")) & " " & ¬
(quoted form of oldFileName) & " > " & (quoted form of newFileName) & " \n "
if runBool is false then return shellString
do shell script shellString
end perlSubstitution
--This function returns a specially formatted date-time string, i.e, "140211-201412".
--It requires a date object as argument, which you can get from item properties
--in Finder and System Events.
on makeDateTime(dateObject)
set shortString to short date string of dateObject
set theList to explode(shortString, "/")
if (count of (LI(1, theList))) = 1 then
setLI(1, theList, ("0" & (item 1 of theList)))
end if
if (count of (item 2 of theList)) = 1 then
setLI(2, theList, ("0" & (item 2 of theList)))
end if
set theDate to (item 3 of theList) & (item 1 of theList) & (item 2 of theList)
set AppleScript's text item delimiters to ""
set theTime to time of dateObject
set theHour to (round (theTime / 3600) rounding down) as string
set hourInt to theHour as integer
set hourSecs to (hourInt * 3600)
set minSecs to ((theTime - hourSecs) / 60)
set theMin to (round minSecs rounding down) as string
set minInt to theMin as integer
set minSecs to (minInt * 60)
set theSec to (theTime - hourSecs - minSecs) as string
if ((count of theHour) = 1) then set theHour to ("0" & theHour)
if ((count of theMin) = 1) then set theMin to ("0" & theMin)
if ((count of theSec) = 1) then set theSec to ("0" & theSec)
return (theDate & "-" & theHour & theMin & theSec) as string
end makeDateTime
--originalFile is full path string. dateObject is a date string created by System Events.
on makeFolderAndMoveOriginalFile(originalFile, dateObject)
set origFileName to itemName(originalFile)
set locationPath to implode(LI({1, -2}, explode(originalFile as text, ":")), ":")
set versionsFolder to locationPath & ":" & origFileName & "__versions"
tell application "System Events"
--First get the location of originalFile:
set theLocation to container of (originalFile as alias)
--Make sure folder "versions" exists in same folder as specified item.
if not (exists folder (origFileName & "__versions") of theLocation) ¬
then my makeFolder(versionsFolder)
end tell
set fileToMove to makeDateTime(dateObject) & "__" & origFileName
--Rename original file so it's preprended with date and time saved,
--and rename new item so it now has original file's name:
do shell script (renameItem(originalFile, fileToMove, false) & ¬
renameItem(originalFile & ".new", origFileName, false))
--Move original file into the versions folder:
moveItem(locationPath & ":" & fileToMove, versionsFolder, true)
end makeFolderAndMoveOriginalFile
on countCheck(theLists)
set x to count of LI(1, theLists)
repeat with i from 2 to count of theLists
if ((count of LI(i, theLists)) ≠x) then return false
end repeat
return true
end countCheck
on userApprovesSettings()
--format the summary into a string by looping thru settingsList:
set summaryString to ""
repeat with i from 1 to (count of choiceNums)
if LI(i, choiceNums) < 3 then --then search is specific.
set searchText to "\"" & LI(i, searchTexts) & "\""
set containedOrSeparate to (return & "Context: " & LI(i, cOrSs))
else --then it's a begin(anything)end search.
set searchText to "\"" & LI(1, LI(i, searchTexts)) & "\" to \"" & LI(2, LI(i, searchTexts)) & "\""
set containedOrSeparate to ""
end if
set replaceText to "\"" & LI(i, replaceTexts) & "\""
set theLine to "Choice: " & LI(LI(i, choiceNums), choiceList) & return & "Replace " & searchText & ¬
" with " & replaceText & containedOrSeparate & return & return
set summaryString to (summaryString & theLine)
end repeat
display dialog "Is this what you wanted?" & return & return & summaryString buttons ¬
{"Cancel", "No, Start Again", "Yes"} default button 3
if button returned of result is "Yes" then
return true
else
return false
end if
end userApprovesSettings
Model: 2009 mac mini
AppleScript: 2.5
Browser: Safari 9.1.2
Operating System: Mac OS X (10.10)