File rename and move into eponymous subfolders

Hello good peoples of macscripter.net :lol:

I came across (read: hijacked and slightly modified) a script that seemed useful to help me take multiple files using a naming convention and parse them by renaming them and moving them into partially-eponymous directories.

– can handle files with and without extensions
– can be modified for a variety of delimiters and naming conventions

A lot to unpack here though - I have many questions being a student of AppleScript.

The overall purpose of the script is to move and rename all my file-variations to subfolders with those subfolders being eponymous to the variation within the name of the file itself.

example of files names: _.
financials_publicview.rtf
financials_serviceview.rtf
financials_customerview-rtf
financials_managementview.rtf

example of folder names and resulting files:
/…//publicview/financials.rtf
/…//serviceview/financials.rtf
/…//customerview/financials.rtf
/…//managementview/financials.rtf

… the script assumes much about the file nomenclature: that the name of the subfolder is always the portion between the (last) delimiter and the dot before the file’s extension, and that there is always at least one delimiter in the file name. *I am sure this can be adjusted to several file naming conventions, delimiters, and so on…

Per the original, this script uses the command line interface ditto which is able to create intermediate directories on-the-fly. It separates name and extension of each selected file and strips the subfolder name from the file name. It’s not necessary to create the subfolders “manually”.



(*

A variation on this most excellent script found here: https://stackoverflow.com/questions/36051457/applescript-rename-and-move-organize-files-to-subfolders

*)



tell application "Finder"
	
        set workingFiles to selection
	
	if workingFiles is {} then return
	set destinationDirectory to POSIX path of (choose folder with prompt "Select a destination for the renamed file(s):")
	(* 
	if you only wanted to use the same folder as selected items, use this: 
	set destinationDirectory to POSIX path of (container of item 1 of workingFiles as text)   
	*)
	
end tell

(* 
Delimiters assumptions: that you're naming your file variations with the convention <name>_<variation>.extension where the extension is optional for this script. You can modify the script to suit your own conventions if needed.
*)
set {TID, text item delimiters} to {text item delimiters, "_"}

repeat with fileIndexer in workingFiles
	
	set {workingFileName, workingfileExtension} to NameExtensionParser(fileIndexer)
	tell text items of workingFileName to set {prefix, suffix} to {items 1 thru -2 as text, item -1}
	set newFilePath to quoted form of (destinationDirectory & suffix & "/" & prefix & workingfileExtension)
	set sourceFile to quoted form of POSIX path of (fileIndexer as text)
	do shell script "/usr/bin/ditto " & sourceFile & space & newFilePath
	(* 
	append this to the shell script statement if you would like to remove source file: & "; /bin/rm " & sourceFile. 
	*)
	
end repeat

set text item delimiters to TID

-- Handler defined for splitting files into names and extensions
on NameExtensionParser(meatball)
	set {name:workingFileName, name extension:workingfileExtension} to meatball
	if workingfileExtension is missing value then return {workingFileName, ""}
	return {text 1 thru ((count workingFileName) - (count workingfileExtension) - 1) of workingFileName, "." & workingfileExtension}
end NameExtensionParser


Question #1
How can I source files by a “choose folder” dialog (much like I can choose a destination folder as the parent directory for final results?) I have tried several iterations of statements… as shown below:

“set workingFiles to every item of (choose file with prompt “Choose the file(s) you would like to move:” with multiple selections allowed) as ?” (where ? == list, item, POSIX file, etc…)

– set workingFiles to every POSIX file of (choose file with prompt “Choose the file(s) you would like to move:” with multiple selections allowed)

…Yet, I just can’t get the right statement working as aliases seem to plague the script and trip errors. If I simply run this script - as is above - after selecting files in the Finder, I have no issues - this script works as published.

Question #2, parts a, b and c :smiley:
For the sake of being as student learning the AS-language, can someone walk me through what these statements and expressions are actaullly doing:

a) “if workingFiles is {} then return”
who dat? I have a hunch that this statement helps with none vs one vs many files selected… but can someone elaborate on what is actually happening? returning the empty set?

b) “tell text items of workingFileName to set {prefix, suffix} to {items 1 thru -2 as text, item -1}”
– here I am confused on text items, the list that is set, and the manipulations to the list (e.g. “items 1 thru -2”, “item -1”). I am not sure of how to understand the mechanics what is happening with these expressions - can someone walk me through this?

c) from the handler: “return {text 1 thru ((count workingFileName) - (count workingfileExtension) - 1) of workingFileName, “.” & workingfileExtension}”
whaaa?? returning by counting through what exactly? (text? 1 thru… ) and why offer up the “-1” for the extension aspect of this? Am I missing the significance of the “text” class?

I appreciate the genius of all of you… thanks in advance for any help you are able to provide.

Model: Crop Circle Mac running on a Infinity Stone
AppleScript: 2.7
Browser: Firefox 57.0
Operating System: Mac OS X 10.13.2

Here are a couple of other versions of the script. The first is probably a little faster, but assumes that all the files have name extensions, which it’s best that they do nowadays anyway.

tell application "Finder"
	activate -- Bring to the front to display the following dialogs.
	
	set sourceDirectory to (choose folder with prompt "Select the folder containing the file(s):")
	set workingFiles to (every file of sourceDirectory) as alias list -- 'as alias list' to get aliases instead of Finder references.
	
	set destinationDirectory to POSIX path of (choose folder with prompt "Select a destination for the renamed file(s):")
	
end tell

(* 
Delimiters assumptions: that you're naming files named with the convention <name>_<variation>.extension, where <variation> does not itself contain "." and the extension is NOT optional. You can modify the script to suit your own conventions if needed.
*)
set {TID, text item delimiters} to {text item delimiters, {"/", "_", "."}}

repeat with thisFile in workingFiles
	
	set sourceFilePath to POSIX path of thisFile
	set {newFileName, newFolderName, extn} to text items -3 thru -1 of sourceFilePath
	set newFilePath to destinationDirectory & newFolderName & ("/" & newFileName) & ("." & extn)
	
	do shell script "/usr/bin/ditto " & quoted form of sourceFilePath & space & quoted form of newFilePath
end repeat

set text item delimiters to TID
tell application "Finder"
	activate -- Bring to the front to display the following dialogs.
	
	set sourceDirectory to (choose folder with prompt "Select the folder containing the file(s):")
	set workingFiles to (every file of sourceDirectory) as alias list -- 'as alias list' to get aliases instead of Finder references.
	
	set destinationDirectory to POSIX path of (choose folder with prompt "Select a destination for the renamed file(s):")
	
end tell

(* 
Delimiters assumptions: that you're naming files named with the convention <name>_<variation>.extension, where <variation> does not contain "." and the extension is optional. You can modify the script to suit your own conventions if needed.
*)
set {TID, text item delimiters} to {text item delimiters, {"/", "_"}}

repeat with thisFile in workingFiles
	
	set sourceFilePath to POSIX path of thisFile
	set {newFileName, newFolderName} to text items -2 thru -1 of sourceFilePath
	-- Act according to whether or not the file's name has an extension.
	if (newFolderName contains ".") then
		set text item delimiters to "."
		set {newFolderName, extn} to text items of newFolderName
		set text item delimiters to {"/", "_"}
		set newFilePath to destinationDirectory & newFolderName & ("/" & newFileName) & ("." & extn)
	else
		set newFilePath to destinationDirectory & newFolderName & ("/" & newFileName)
	end if
	
	do shell script "/usr/bin/ditto " & quoted form of sourceFilePath & space & quoted form of newFilePath
end repeat

set text item delimiters to TID

When asked for its selection, the Finder returns a list of all the disk items selected in its frontmost window. If only one item’s selected, the result’s still a list, but there’s only one item in it. If nothing’s selected, an empty list is returned.

There are two shorthands here, setting by list and doing so within a ‘tell’ statement.

In setting by list, all the items in the left-hand list must be settable things (such as variables, list positions, etc.) and all the items in the right hand list must be values or references thereto. The items in the left list are set to the corresponding values in the right list. If there are fewer items to set than there are values, the spare values aren’t used. If there are fewer values than items to set, the process errors. The setting is done in order from left to right, BUT all the values on the right are got before that begins. So in a line like …

set {TID, text item delimiters} to {text item delimiters, "_"}

… the current value of text item delimiters and “_” are both got before TID is set to the first and text item delimiters to the second.

In a ‘tell’ statement involving something which has its own elements or properties, the reference to the told item in references to its elements or properties is understood. So …

tell text items of workingFileName to set {prefix, suffix} to {items 1 thru -2 as text, item -1}

… is the same as …

set {prefix, suffix} to {items 1 thru 2 of text items of workingFileName as text, item -1 of text items of workingFileName}

… only slightly less to type. I think in this case, though, the author really wanted:

tell workingFileName to set {prefix, suffix} to {text items 1 thru -2 as text, text item -1}

I’m with you on that one. :wink:

count counts the basic elements of the thing to which it’s applied — unless told otherwise. The basic element in a text is the ‘character’, so counting a text returns the number of characters in it. Counting a list returns the number of items in the list. But you could, if needed, count, say, the paragraphs in a text or the integers (ignoring other items) in a list.

The code above is counting the characters in a file name, counting the characters in its extension, subtracting the result, and subtracting an extra 1 for the “.”. That leaves the number of characters before the dot in the file name. The result’s then used in a range reference to extract that part of the file name from the whole. In longhand, it might be:

set lengthBeforeDot to (count workingFileName) - (count workingfileExtension) - 1
set nameBeforeDot to text 1 thru lengthBeforeDot of workingFileName
set dotAndExtension to "." & workingfileExtension

return {nameBeforeDot, dotAndExtension}

Thanks Nigel. :slight_smile:

I will attempt to digest these alternatives and update the thread in time…