Finder object / text name / related confusion...

I am working with the following code:


-- set the parameters / variables
set foldersFinal to {}
set foldersIntermediate to {}
set foldersTemporary to {}
set {TID, text item delimiters} to {text item delimiters, ","}

set folderTarget to (choose folder with prompt "Choose the disk / directory / folder" default location (path to documents folder))

tell application "Finder"
	
	set foldersFinal to folders of folderTarget as text
	copy foldersFinal to foldersIntermediate  -- Because I don't want two references to the same list 
	
	set noMoreFolders to false
	
	repeat while (noMoreFolders is equal to false) -- Recursively get the sub-folders of all sub-folders
		
		repeat with i from 1 to (count of foldersIntermediate)
			set end of foldersTemporary to folders of item i of foldersIntermediate as text
		end repeat
		
		if ((count of foldersTemporary) is less than 1) then
			set noMoreFolders to true
		else
			set end of foldersFinal to foldersTemporary
			set foldersIntermediate to copy of foldersTemporary
			set foldersTemporary to {}
		end if
	end repeat
	
	
end tell

set text item delimiters to TID

The idea is simple…use Finder’s “folders of fileRef” command to recursively create a list of all sub-folders in a given folder [note: although I am aware that there are more efficient and other ways of doing this I would like to focus on getting this method to work].

The script – as coded – does not work and is kicking up error messages that it cannot get make the class of certain strings…when I played with the code by adding / removing “as text references” etc. I got different things to work [the good news] and different things to not work [the bad news].

With that I think the problem is that I am confused between trying to reference folders outside of a Finder tell block [where the references do not refer to Finder objects] versus inside a Finder tell block [where the references do refer to Finder objects and thus have all of their related properties.]

Would appreciate your assistance in getting this to work.

Thanks,

Joel

Hi,

the main problem is this line


 set foldersFinal to folders of folderTarget as text

with text item delimiters set to comma you get one long string where the HFS paths are separated by comma, but not a list.
As foldersIntermediate is a string this expression

count of foldersIntermediate

counts the characters of the string and this line must fail


set end of foldersTemporary to folders of item i of foldersIntermediate as text

because item i of foldersIntermediate has to be an alias or Finder specifier

This line does not compile due to wrong syntax


set foldersIntermediate to copy of foldersTemporary

copy is a verb, not a noun

StefanK:

Appreciate the response and the pointer in the right direction which enabled me to get the script to work.

The new script is as follows:


-- A script to compile a list of files -- filesFinal -- in the selected directory

-- Set the parameters / variables
set filesFinal to {} -- The FINAL list of files
set filesTemporary to {} -- The TEMPORARY list of files
set foldersFinal to {} -- The FINAL list of folders
set foldersIntermediate to {} -- The INTERMEDIATE list of folders
set foldersTemporary to {} -- The TEMPORARY list of folders

-- Select the folder to compile a list of files for
set folderTarget to (choose folder with prompt "Choose the disk / directory / folder whose file lengths' will be compared against maxCount" default location (path to documents folder))

tell application "Finder"
	--Get all folders (but not subfolders) in folderTarget
	set foldersFinal to folders of folderTarget
	copy foldersFinal to foldersIntermediate -- Copy command (rather than set command) is used so as to not "overwrite" foldersFinal when subsequently manipulating foldersIntermediate
	
	set noMoreFolders to false
	
	-- Get (through iteration) all subfolders in folderTarget
	repeat while (noMoreFolders is equal to false)
		repeat with i from 1 to (count of foldersIntermediate)
			set foldersTemporaryAdditions to folders of item i of foldersIntermediate
			if ((count of foldersTemporaryAdditions) is greater than or equal to 1) then set foldersTemporary to foldersTemporary & foldersTemporaryAdditions
		end repeat
		
		if ((count of foldersTemporary) is less than 1) then
			set noMoreFolders to true
		else
			set foldersFinal to foldersFinal & foldersTemporary
			copy foldersTemporary to foldersIntermediate -- Copy command (rather than set command) is used so as to not "overwrite" foldersIntermediate when resetting foldersTemporary
			set foldersTemporary to {}
		end if
	end repeat
	
	-- Get the files in all subfolders of folderTarget
	repeat with j from 1 to count of foldersFinal
		set filesTemporary to files in item j of foldersFinal
		if ((count of filesTemporary) is greater than or equal to 1) then set filesFinal to filesFinal & filesTemporary
	end repeat
	
	-- Get the files in folderTarget
	set filesTemporary to files in folderTarget
	if ((count of filesTemporary) is greater than or equal to 1) then set filesFinal to filesFinal & filesTemporary
end tell


-- Test that all files and folders were processed
log "Total files are " & (count of filesFinal)
log "Total folders are " & (count of foldersFinal)
log "Total files and folders to compare to Finder's Get Info are " & ((count of filesFinal) + (count of foldersFinal) + 1) -- The 1 is for the parent folder

The remaining “problem” is that this script which I prefer [because I understand it better given my current programming expertise relative] to the other methods which you, and others, have kindly posted for me, some of which are one or two liners is slower than those methods.

With that, I would appreciate any suggestions you may have as to how the script – using its logic / process – can be made to run faster.

With much thanks,

Joel

As you are only counting the files, this is sufficient (and faster)
The script uses a real recursive function


property numberOfFolders : 0
property numberOfFiles : 0

set folderTarget to (choose folder with prompt "Choose the disk / directory / folder whose file lengths' will be compared against maxCount" default location (path to documents folder))
tell application "Finder" to set mainFolderHasSubFolders to (count (get folders of folderTarget)) > 0
if mainFolderHasSubFolders then processFolders(folderTarget)

-- Test that all files and folders were processed
log "Total files are " & numberOfFiles
log "Total folders are " & numberOfFolders
log "Total files and folders to compare to Finder's Get Info are " & (numberOfFiles + numberOfFolders + 1) -- The 1 is for the parent folder

on processFolders(_folder)
	tell application "Finder"
		set subFolders to folders of _folder
		set countSubFolders to count subFolders
		set numberOfFiles to numberOfFiles + (count (get files of _folder))
	end tell
	if countSubFolders > 0 then
		set numberOfFolders to numberOfFolders + countSubFolders
		repeat with aSubFolder in subFolders
			processFolders(aSubFolder)
		end repeat
	end if
end processFolders


StefanK:

I understand your edits – counting the files and folders (as opposed to the creating a list of their pathnames) and using a handler (as opposed to a repeat loop) and why it is faster.

The points to note – and apologies for any confusion / misdirection on my part – are that I actually need / want the list of pathnames for subsequent processing and the statistics (count of files and count of folders) is included for testing purposes.

With that, I will edit my script to more efficiently use a handler.

With much thanks,

Joel

PS. Happy New Year as well!

A different but similar problem (hence its inclusion in this thread)…

I am working with two related scripts as follows…this script works:


set bracketLeft to "("
set bracketRight to ")"
set dashMark to "-"
set plusSign to "+"
set spaceMark to " "

tell application "Contacts"
	activate
	
	repeat with aPerson in people
		set personList to aPerson
		set personNameFirst to first name of personList
		set personNameLast to last name of personList
		set personPhonesLabel to label of phones of personList
		set personPhonesValue to value of phones of personList
					
		repeat with i from 1 to (count of personPhonesValue)
			set phoneString to ""
			repeat with aCharacter in item i of personPhonesValue
				if ((text of aCharacter is equal to bracketLeft) is true) then set aCharacter to ""
				if ((text of aCharacter is equal to bracketRight) is true) then set aCharacter to ""
				if ((text of aCharacter is equal to dashMark) is true) then set aCharacter to "."
				if ((text of aCharacter is equal to plusSign) is true) then set aCharacter to ""
				if ((text of aCharacter is equal to spaceMark) is true) then set aCharacter to "."
				set phoneString to phoneString & aCharacter
			end repeat
			set item i of personPhonesValue to phoneString
		end repeat
		
		log items in personPhonesValue		
	end repeat	
end tell

…but I needed “more control” in reformatting the telephone numbers so I thought as a first step I would modify a portion the above script as follows to ensure it provided the same functionality before performing my additional manipulations:


		repeat with i from 1 to (count of personPhonesValue)
			set phoneString to ""
			repeat with j from 1 to count of characters of item i of personPhonesValue
				if ((text of character j of item i of personPhonesValue is equal to bracketLeft) is true) then set character j of item i of personPhonesValue to ""
				if ((text of character j of item i of personPhonesValue is equal to bracketRight) is true) then set character j of item i of personPhonesValue to ""
				if ((text of character j of item i of personPhonesValue is equal to dashMark) is true) then set character j of item i of personPhonesValue to "."
				if ((text of character j of item i of personPhonesValue is equal to plusSign) is true) then set character j of item i of personPhonesValue to ""
				if ((text of character j of item i of personPhonesValue is equal to spaceMark) is true) then set character j of item i of personPhonesValue to "."
				set phoneString to phoneString & character j of item i of personPhonesValue
			end repeat
			set item i of personPhonesValue to phoneString
		end repeat

…but the above code does not work noting that specifically it complains about the “set character j of item i of personPhoneValue” [i.e. it does not allow the “reset”] with the error messages stating that it can not perform the “set command”.

Would appreciate assistance in solving this noting that I have tried a bunch of combination of adding “text of” in various places, etc. but cannot get it to go.

Thanks in advance,

Joel

Hello.

Suddenly I remembered having written this. It is a handler that traverses a directory tree, which you pass in a script object to do whatever you want to do with some or all files, as the directory tree is traversed.

I hope it helps a little. :slight_smile:

Hi,

equation checks in repeat with . in loops don’t work because the list items are references.
It’s necessary to deference the item first

if ((text of aCharacter is equal to bracketLeft) is true)

does exactly the same as

if Character = bracketLeft

try something like this


repeat with i from 1 to (count personPhonesValue)
	set phoneString to ""
	repeat with aCharacter in item i of personPhonesValue
		set aChar to contents of aCharacter -- dereference the list item
		if aChar = bracketLeft then
			set aCharacter to ""
		else if aChar = bracketRight then
			set aCharacter to ""
		else if aChar = dashMark then
			set aCharacter to "."
		else if aChar = plusSign then
			set aCharacter to ""
		else if aChar = space then
			set aCharacter to "."
		else
			set aCharacter to aChar
		end if
		set phoneString to phoneString & aCharacter
	end repeat
	set item i of personPhonesValue to phoneString
end repeat


AppleScript strings are immutable: once you have one, you can’t change it – you have to make a new one. But what you’re doing (search and replace) is usually best done in AS using text item delimiters. Here’s your script rewritten to use them, in a handler you can re-use:

repeat with i from 1 to (count of personPhonesValue)
	set phoneString to (my swapFromThese:{bracketLeft, bracketRight, plusSign} toThis:"" inThis:(item i of personPhonesValue))
	set phoneString to (my swapFromThese:{dashMark, spaceMark} toThis:"." inThis:phoneString)
	set item i of personPhonesValue to phoneString
end repeat

on swapFromThese:changeList toThis:replaceString inThis:theText
	set saveTID to AppleScript's text item delimiters
	set AppleScript's text item delimiters to changeList
	set aList to text items of theText
	set AppleScript's text item delimiters to {replaceString}
	set theText to aList as text
	set AppleScript's text item delimiters to saveTID
	return theText
end swapFromThese:toThis:inThis:

@ McUsrII, appreciate the response and will take a look later tonight when I return from dinner later this evening.

@ StefanK and Shane, similarly appreciate the response and will also take a look later tonight when I return from dinner…that said, you both were kind enough to explain to me that i) references cannot be changed and ii) to get this script to work I need to de-refrence the reference…and, so that I understand what can and cannot be changed more fully, would you be so kind to explain:

  1. What is meant by “references” and why they cannot be changed; and

  2. Which AppleScript scripts cannot be changed [i.e. all?] as I not yet run into this.

With much appreciate and thanks for your patience and teaching.

Joel

I’m not sure that references were the crux of the issue here. The error message is telling you that you can’t modify a string. You were trying to do the equivalent of:

set aString to "abcde"
set character 2 of aString to "x"

You can’t do that. Instead, you need to create a new string, as Stefan showed.

Shane:

I think I understand your above explanation as to what I can / cannot do. It is my understanding that Stefan’s code solves the problem because instead of replacing a character within a string he is replacing the entire string (albeit a one character string) [i.e. aChar is a single character whereas personPhonesValue is multiple characters] or is this the very point. Is this correct?

The other problem is that Stefan’s method does not solve my problem because I need to examine “two characters” at a time for some of the other manipulations / replacements I need to make as noted in post #6 to this thread.

Will try to get your method to work which it hopefully will!

Thanks,

Joel

In effect, he’s replacing the string, but he’s actually creating a new string.

Agreed in that aChar is a new string but the point I was trying to make that aCharacter can be changed because he is changing all of aCharacter and not a part of aCharacter…to drive this point home (while stealing from you example):

  1. This WILL NOT work because it attempts to change a character within a string of characters

set aString to "abcde"
set character 2 of aString to "x"

BUT

  1. This WILL work because it changes the entire string.

set aString to "abcde"
set aString to "x"

Is this correct and, if not, then what am I missing?

Thanks for your help and patience.

Joel

No, that doesn’t actually change the string. It creates a whole new string, and then assigns it to the same variable (effectively discarding the old string). It may seem like semantics, but it’s an important distinction.

Whereas most other classes are mutable, at least to some degree. So when you say “set item 1 of aList to 1”, you are actually modifying the list.

It’s at least one of the cruxes. :wink:
Consider with this code “ which does not dereference the list items “ no character is replaced at all
because aCharacter = bracketLeft is treated as item 1 of item 1 of {“()”} = bracketLeft


set bracketLeft to "("
set bracketRight to ")"

set personPhonesValue to {"()"}

repeat with i from 1 to (count personPhonesValue)
	set phoneString to ""
	repeat with aCharacter in item i of personPhonesValue
		if aCharacter = bracketLeft then
			set aChar to ""
		else if aCharacter = bracketRight then
			set aChar to ""
		else
			set aChar to aCharacter
		end if
		set phoneString to phoneString & aChar
	end repeat
	set item i of personPhonesValue to phoneString
end repeat


Yes, that’s a potential issue. But it’s not what the OP used – he wrote:

And in that, “text of” does the dereferencing.

I am still a little fuzzy – more on this in my next post – but I think I see your point in that:

  1. "set aString to “abcde” creates a variable aString and assigns it the string “abcde”

  2. “set aString to x” assigns the variable a different string [i.e. "x’]

    1. and 2. work because at no point does it attempt to change the string “abcde”

Is this at least correct?

The second point – focusing on semantics – applies to you last few words in that the issue is not so much changing a list but is more changing a portion of a string within a list…is this correct?

I played around further with the various codes provided by both Stefan and you.

While your code works – and I thank you for that – I am still confused by Stefan’s code and the meaning of dereferencing which I want to learn and fully understand.

With that note that the following script works:


set L3 to {"abc", "def", "ghi", "jkl"}
set newEntry to ""
repeat with aCharacter in item 3 of L3
	set aChar to contents of aCharacter
	if aChar = "g" then
		set tempVar to "z"
	else
		set tempVar to aChar
	end if
	set newEntry to newEntry & tempVar
end repeat
set item 3 of L3 to newEntry
log items of L3
--> {"abc", "def" "zhi","jkl"}

but were I to change the code “set aChar to contents of aCharacter” to “set aChar to aCharacter” or “copy aCharacter to aChar” then the script no longer works which was surprising to me because in either case I am creating a new variable [i.e. aChar] and referencing or working on it [i.e. rather than aCharacter].

So – apologetically – my confusion continues:

  1. What does dereferencing mean?

  2. Why / how does the term “contents” [or, “text” as you note in post #17] cause or result in the dereferencing.

And, to get the ball rolling, the only thing I can think of is that somehow [and an explanation to this would be most helpful] the use of the word “contents” or “text” breaks the “linkage” between “item 3 of L3” [i.e. when I set aChar to aCharacter without the use of “contents” or “text” then aChar is assigned the value to aCharacter which in turn is dependent on “item 3 of L3” which creates the linkage].

Admittedly this too is likely incorrect because it does not explain why the use of the “copy” command does not break the linkage because based on the texts that I have read, the use of the “copy” command creates a “new reference / string” that is not linked back to the original [i.e. the code “set L3 to {a,b,c,d}” followed by “copy L3 to L4” results in the creation of a second {a,b,c,d} list such that manipulation of the L4 list leaves the original L3 list unchanged]

Thanks again,

Joel

Regarding list item references please read
section repeat with loopVariable (in list) in
AppleScript Language Guide: Control Statements Reference
and section reference in
AppleScript Language Guide: Class Reference