-- 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.
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
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.
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
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.
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.
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.
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:
What is meant by “references” and why they cannot be changed; and
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.
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.
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!
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):
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
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?
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.
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
I am still a little fuzzy – more on this in my next post – but I think I see your point in that:
"set aString to “abcde” creates a variable aString and assigns it the string “abcde”
“set aString to x” assigns the variable a different string [i.e. "x’]
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:
What does dereferencing mean?
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]