Batch File Renamer

Hey, I wrote this file & folder renaming droplet, and here is the code. Save it as an application (it’s only written to be used as a droplet). It can do (almost) everything that Name Mangler can do.


(*
Batch Renamer:  renames batches of files and/or folders.
*)

property thePs : {} --static global variable, stands for 'parameters'
property uniqueString : "¯¯¯¯¯"
global fr, ns, nsacn, oa, oaacn, aps, irc, ce

--Handler runs when dragging items onto app icon:
on open draggedItems
	mainRoutine(draggedItems)
end open



--DEFINE FUNCTIONS:

on mainRoutine(draggedItems)
	try
		--Convert every item in draggedItems to string paths:
		repeat with i from 1 to (count of draggedItems)
			setLI(i, draggedItems, LI(i, draggedItems) as text)
		end repeat
		--Every option (except for 'change extension') will not affect the name extension.
		set {fr, ns, nsacn, oa, oaacn, aps, irc, ce} to {"Find and Replace", "Number Sequentially", ¬
			"Number Sequentially, attach to current name", "Order Alphabetically", ¬
			"Order Alphabetically, attach to current name", "Add Prefix/Suffix", ¬
			"Insert/Remove Characters", "Change Extension"}
		repeat
			--Have user make main choice:
			set userChoice to listChoice({fr, ns, nsacn, oa, oaacn, aps, irc, ce}, ¬
				"What Shall I Do With These File Names?", "Proceed")
			if userChoice is false then return --app quits.
			--Present a set of additional choices based on what user has chosen already:
			set thePs to additionalChoices(userChoice)
			if thePs is false then return --app quits.
			
			--Now rename each dragged item.  If renameItems() returns true,
			--then the app can quit.
			if renameItems(draggedItems, userChoice, thePs) is true then exit repeat
		end repeat
	on error
		return
	end try
end mainRoutine


on additionalChoices(userChoice)
	try
		set thePs to setParameters(userChoice)
		if userChoice is fr then
			set thePs's caseBool to button returned of ¬
				dialog(0, "Should the Find and Replace be case-sensitive?", {"Cancel", "Yes", "No"})
			set thePs's searchString to text returned of ¬
				dialog("", "Enter the text to replace:", {"Cancel", "Proceed"})
			set thePs's replaceString to text returned of ¬
				dialog("", "Enter the text to replace it with:", {"Cancel", "Proceed"})
		else if userChoice is in {ns, nsacn} then
			set thePs's curN to text returned of dialog("001", ¬
				"Enter a starting number, including total number of digits desired:", {"Cancel", "Proceed"})
			if userChoice is nsacn then
				set thePs's attachBeginOrEnd to button returned of dialog(0, ¬
					"Attach at beginning or end of current name?", {"Cancel", "End", "Beginning"})
			end if
			if userChoice is ns or thePs's attachBeginOrEnd is "Beginning" then
				set befOrAft to "after"
			else
				set befOrAft to "before"
			end if
			set thePs's additionalTxt to text returned of dialog("", ¬
				"Enter any additional text you want placed " & befOrAft & " the number:", ¬
				{"Cancel", "Proceed"})
		else if userChoice is aps then
			set listChoice to listChoice({"Prefix", "Suffix", "Both"}, ¬
				"Add a Prefix, Suffix, or Both?", "Proceed") as text
			if listChoice is "Both" or listChoice is "Prefix" then
				set thePs's pfx to text returned of dialog("", "Enter the Prefix:", {"Cancel", "Proceed"})
			end if
			if listChoice is "Both" or listChoice is "Suffix" then
				set thePs's sfx to text returned of dialog("", "Enter the Suffix:", {"Cancel", "Proceed"})
			end if
			
		else if userChoice is irc then
			set thePs's listChoice to listChoice({"Insert", "Remove"}, "Insert Text or Remove Text?", ¬
				"Proceed") as text
			if thePs's listChoice is "Remove" then
				set thePs's removeFromWhere to listChoice({"The Left", "The Right"}, ¬
					"Remove starting from which end?", "Proceed") as text
				set thePs's removeStartPosition to text returned of dialog("1", "Enter the starting position:", ¬
					{"Cancel", "Proceed"}) as integer
				set thePs's origRemoveNum to text returned of dialog("", "Enter the number of characters to remove:", ¬
					{"Cancel", "Proceed"}) as integer
			else if thePs's listChoice is "Insert" then
				set thePs's insertFromWhere to listChoice({"The Left", "The Right"}, ¬
					"Insert starting from which end?", "Proceed") as text
				set thePs's insertStartPosition to text returned of dialog("1", "Enter the starting position:", ¬
					{"Cancel", "Proceed"}) as integer
				set thePs's insertTxt to text returned of dialog("", "Enter the text to insert:", {"Cancel", "Proceed"})
			end if
			
		else if userChoice is ce then
			set thePs's newExt to text returned of dialog("", "Enter the new extension:", {"Cancel", "Proceed"})
			
		else if userChoice is in {oa, oaacn} then
			set thePs's curN to text returned of dialog("aaa", ¬
				"Enter a starting letter combo, including total number of characters desired:", {"Cancel", "Proceed"})
			if userChoice is oaacn then
				set thePs's attachBeginOrEnd to button returned of dialog(0, ¬
					"Attach at beginning or end of current name?", {"Cancel", "End", "Beginning"})
			end if
			if userChoice is oa or thePs's attachBeginOrEnd is "Beginning" then
				set befOrAft to "after"
			else
				set befOrAft to "before"
			end if
			set thePs's additionalTxt to text returned of dialog("", ¬
				"Enter any additional text you want placed " & befOrAft & " the letters:", ¬
				{"Cancel", "Proceed"})
		end if
	on error
		return false
	end try
	return thePs
end additionalChoices




on renameItems(draggedItems, userChoice, thePs)
	set {modifiedItemsPaths, newNames} to {{}, {}}
	--Process each dragged item:
	repeat with i from 1 to count of draggedItems
		--Create new name string:
		set newName to newNameString(LI(i, draggedItems), userChoice, thePs)
		set end of newNames to newName
		--Create new path to item with new name:
		set pathList to explode((LI(i, draggedItems)), ":")
		if pathList ends with "" then set pathList to LI({1, -2}, pathList)
		setLI(-1, pathList, newName)
		set end of modifiedItemsPaths to implode(pathList, ":")
	end repeat
	set {i, previewString} to {1, ""}
	--Create a string for previewing the new names:
	repeat with i from 1 to count of newNames
		if i = 21 then
			set previewString to previewString & "and so on...."
			exit repeat
		end if
		set previewString to previewString & replace(uniqueString, "", LI(i, newNames)) & return
		set i to (i + 1)
	end repeat
	--Show the user a preview of the new names:
	set theResult to dialog(0, "Is this what you wanted?" & return & return & ¬
		previewString, {"Cancel", "No, Go Back", "Yes"})
	if button returned of theResult is "Cancel" then return true
	if button returned of theResult is "No, Go Back" then return false
	--Do the actual renaming:
	renameAll(draggedItems, newNames)
	
	--Now remove the unique suffix:
	removeUniqueStrings(modifiedItemsPaths)
	return true
end renameItems


--Increments theNum that theItem will be renamed with, and converts it back to text.
--If the num requires zeros at the beginning, it adds them.
on incrementNum(theNum)
	if class of theNum is not text then set theNum to (theNum as text)
	--Increment curN:
	set numLength to (count of theNum)
	set theNum to (theNum as integer)
	set theNum to ((theNum) + 1)
	set theNum to (theNum as text)
	--If there need to be zeros at beginning of curN, make them:
	if (count of theNum) is not numLength then
		set numZeros to (numLength - (count of theNum))
		set theZeros to ""
		repeat numZeros times
			set theZeros to (theZeros & "0")
		end repeat
		set theNum to (theZeros & theNum)
	end if
	return theNum
end incrementNum


--theItem must be string path to the item. Returns the name (without its extension), and the extension
--as separate items.
on itemName(theItem)
	set theName to itemNameWithExt(theItem)
	set theList to explode(theName, ".")
	if (count of theList) = 1 then
		set theExt to ""
	else
		set theExt to "." & (LI(-1, theList) as text)
		set theName to replace(theExt, "", theName) --removes extension from theName
	end if
	return {theName, theExt}
end itemName






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


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


on incrementAlphabet(theChars)
	set charList to every text item of theChars
	set theOrder to ¬
		{"a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z"}
	repeat with i from 1 to count of charList
		if LI(-i, charList) is "z" then
			setLI(-i, charList, "a")
			if i = (count of charList) then
				set charList to "a" & (charList as text)
			end if
		else
			set thePos to getIndex(LI(-i, charList), theOrder)
			setLI(-i, charList, LI(thePos + 1, theOrder))
			exit repeat
		end if
	end repeat
	return charList as text
end incrementAlphabet



on dialog(defaultTxt, theMessage, theButtons)
	set lastButton to count of theButtons
	try
		if defaultTxt = 0 then
			return display dialog theMessage with title "BatchRenamer" buttons theButtons ¬
				default button lastButton
		else
			return display dialog theMessage with title "BatchRenamer" default answer ¬
				defaultTxt buttons theButtons default button lastButton
		end if
	on error
		return false
	end try
end dialog

--The last 3 parameters must be strings.
on listChoice(theList, thePrompt, okButton)
	try
		return (choose from list theList with title ¬
			"BatchRenamer" with prompt thePrompt OK button name okButton) as text
	on error
		return false
	end try
end listChoice

--theItem must be string path.
on removeUniqueStrings(renamedItems)
	repeat with i from 1 to (count of renamedItems)
		set theName to LI(-1, explode(LI(i, renamedItems) as string, ":")) --extracts item name from path.
		--if theName is "" then the item is a folder and the path must have ended with a colon:
		if theName is "" then set theName to LI(-2, explode(LI(i, renamedItems) as string, ":"))
		set newName to replace(uniqueString, "", theName) --removes uniqueString.
		tell application "System Events" to set name of item (my LI(i, renamedItems)) to newName
	end repeat
end removeUniqueStrings




--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 itemNameWithExt(theItem)
	set theParts to explode(theItem, ":")
	if theParts ends with "" then set theParts to LI({1, -2}, theParts)
	return LI(-1, theParts) as text
end itemNameWithExt




on renameAll(theItems, theNames)
	repeat with i from 1 to count of theItems
		tell application "System Events" to set name of item ¬
			(my LI(i, theItems)) to my LI(i, theNames)
	end repeat
end renameAll


on newNameString(theItem, userChoice, thePs)
	set {theName, theExt} to itemName(theItem)
	if userChoice is fr then
		if thePs's caseBool is "Yes" then
			considering case
				set newName to replace(thePs's searchString, thePs's replaceString, theName)
			end considering
		else
			set newName to replace(thePs's searchString, thePs's replaceString, theName)
		end if
	else if userChoice is ns then
		set newName to (thePs's curN & thePs's additionalTxt)
		set thePs's curN to incrementNum(thePs's curN)
	else if userChoice is nsacn then
		if thePs's attachBeginOrEnd is "Beginning" then -- Then place the number at beginning of name.
			set newName to (thePs's curN) & (thePs's additionalTxt) & theName
		else -- Then place the number at end of name, with the additionalTxt coming just before it.
			set newName to (theName & (thePs's additionalTxt) & (thePs's curN))
		end if
		set thePs's curN to incrementNum(thePs's curN)
	else if userChoice is oa then
		set newName to (thePs's curN & thePs's additionalTxt)
		set thePs's curN to incrementAlphabet(thePs's curN)
	else if userChoice is oaacn then
		if thePs's attachBeginOrEnd is "Beginning" then -- Then place the number at beginning of name.
			set newName to (thePs's curN) & (thePs's additionalTxt) & theName
		else -- Then place the number at end of name, with the additionalTxt coming just before it.
			set newName to theName & (thePs's additionalTxt) & (thePs's curN)
		end if
		set thePs's curN to incrementAlphabet(thePs's curN)
	else if userChoice is aps then
		set newName to (thePs's pfx & theName & thePs's sfx)
	else if userChoice is irc then
		if thePs's listChoice is "Remove" then
			set thePs's removeNum to thePs's origRemoveNum
			set {x, thePs's removeNum} to {(thePs's removeStartPosition), ((thePs's removeNum) - 1)}
			--if theItem is "Macintosh HD:Applications:AppleScript:Applications:BatchRenamer project:0020.scpt" then return thePs's removeNum
			--If x is too many characters in from left or right, skip this item:
			if (x > (count of theName)) or ((thePs's removeNum) ≥ (count of theName)) then return
			if (thePs's removeFromWhere is "The Right") then set {x, thePs's removeNum} ¬
				to {-(thePs's removeStartPosition), -(thePs's removeNum)}
			set y to (x + (thePs's removeNum))
			--set removalText to (characters x thru y of theName as text)
			set nameChars to every text item of theName
			if thePs's removeFromWhere is "The Left" then
				--If the removeStartPosition is not at beginning of name, save first part of name:
				if x > 1 then
					set firstPart to implode(LI({1, (x - 1)}, nameChars), "")
				else
					set firstPart to ""
				end if
				--Set x to the first char that comes after the section of theName to be removed:
				set x to (y + 1)
				--If x now exceeds the number of chars in theName, this means the user wanted to trim 
				--off the last character(s) of theName:
				if x > (count of theName) then
					set newName to firstPart
				else
					set newName to (firstPart & (LI({x, -1}, nameChars) as text)) --(replace(removalText, "", theName))
				end if
			else
				--If the removeStartPosition is not at end of name, save last part of name:
				if x < -1 then
					set lastPart to implode(LI({-1, (x + 1)}, nameChars), "")
				else
					set lastPart to ""
				end if
				--Set x to the first char that comes before the section of theName to be removed:
				set x to (y - 1)
				--If positive x now exceeds the number of chars in theName, this means the user wanted to trim 
				--off the first character(s) of theName:
				if (-(x) > (count of theName)) then
					set newName to lastPart
				else
					set newName to ((LI({1, x}, nameChars) as text) & lastPart)
				end if
			end if
		else if thePs's listChoice is "Insert" then
			set x to (thePs's insertStartPosition)
			if thePs's insertFromWhere is "The Left" then
				if x > (count of theName) then set {x, thePs's insertFromWhere} to {-1, "The Right"}
				if x > 1 then
					set newName to (((characters 1 thru (x - 1) of theName as text) & ¬
						thePs's insertTxt & characters x thru -1 of theName as text))
				else
					set newName to (thePs's insertTxt & (characters x thru -1 of theName as text))
				end if
			end if
			if thePs's insertFromWhere is "The Right" then
				if (x > 0) then set x to -(thePs's insertStartPosition)
				if x < -1 then
					set newName to (characters 1 thru x of theName as text) & thePs's insertTxt & ¬
						(characters (x + 1) thru -1 of theName as text)
				else
					set newName to (theName & thePs's insertTxt)
				end if
			end if
		end if
	end if
	if userChoice is ce then
		set newName to (uniqueString & theName & "." & thePs's newExt)
	else --If it's any of the other choices:
		set newName to (uniqueString & newName & theExt)
	end if
	return newName
end newNameString


on setParameters(userChoice)
	set thePs to {origRemoveNum:0}
	if userChoice is in {ns, nsacn, oa, oaacn} then
		set thePs to thePs & {curN:"", additionalTxt:"", attachBeginOrEnd:""}
	else if userChoice is irc then
		set thePs to thePs & {listChoice:"", insertStartPosition:0, removeStartPosition:0, insertFromWhere:¬
			"", removeFromWhere:"", removeNum:0, insertTxt:""}
	else if userChoice is in {ce, aps} then
		set thePs to thePs & {pfx:"", sfx:"", newExt:""}
	else if userChoice is fr then
		set thePs to thePs & {caseBool:"", searchString:"", replaceString:""}
	end if
	return thePs
end setParameters


Model: late 2009 mac mini
AppleScript: 2.3.2
Browser: Safari 537.86.3
Operating System: Mac OS X (10.9.5)