I want to write a recursive routine that drills down through folders to find the last remaining folder without a sub folder. When this condition is met, then another handler is run on any files in that ultimate folder. then I want to move up a folder level, and drill down into any other folders there - etc.
The folder structure will be at least three levels, deep, but may contain more. My question is, can this be done by calling itself?
on drill_down(counter)
listfolders of a path
go to folder #‘counter’
set new counter = number of folders in this level
repeat with i = 1 to newcounter
drill down(newcounter)
end drill_down
Something like that. IS this syntactically (sp?) legal?
…but yes, recursive handlers work just fine in AppleScript. Make sure the handler returns an output it can use as input and include a proper termination of the recursion to prevent endless looping. Here’s a simple working sample:
on factorialClosingWith(n, p) -- with n>p; = n.(n-1).(n-2). ... .(p+1).p (recursive algorithm)
if n < p then -- errorcheck; switch n with p and proceed
copy n to j
copy p to n
copy j to p
end if
if p = 0 then set p to 1
if n ≤ p then
return p -- terminates the recursion (to prevent endless loop)
else
return n * (factorialClosingWith(n - 1, p)) -- handler calls itself here
end if
end factorialClosingWith
HTH
Jan
Model: Alu-PowerBook 15" 1.25 GHz
AppleScript: 1.10
Browser: Safari 417.8
Operating System: Mac OS X (10.4)
Jan - thanks for the reply! Glad to know it will work. (tested your script - it worked great, and I was able to get a stack overflow quickly by trying factorial 720 - lets me know how fast applescript does a simple loop)
Follow up question - how do I get applescript to list files and folders in a folder, and then return the path to item 1, item 2 etc.?
set item_list to list folders of folder path
returns a simple list of the two or three names of the folders within. When I try to get the path to those objects, applescript chokes, because I am trying to get a path to a simple text string.
My code is:
on drill_down(fpath)
set item_list to list folder of fpath without invisibles
get path to item 1 of item_list
end drill_down
This generates an error ‘folder does not exist’. When I return ‘item_list’ it shows {“2006” , “2007”} - the names of the two folders within fpath.
I want to get the PATH to those items. I can see a work around by storing fpath as a string, and doing some fancy footwork in concatenating the names one by one, but that looks like a big pain, and a lot of extra code for something that is probably built into applescript! Surely enough people want paths to items in a container that this is part of the language. Just don’t know the syntax to extract it.
so - five levels of nesting, with some files at the fourth level in Jan. Here is the drill_down code:
on drill_down(fpath)
tell application "Finder"
set item_list to list folder fpath without invisibles
set counter to count of item_list
set fpath to fpath as string
repeat with i from 1 to counter
set this_item to item i of the item_list
set this_item to (fpath & this_item) as alias
set this_info to info for this_item
set the container_name to the name of container of this_item
if kind of this_info is "Folder" then
open this_item
my drill_down(this_item)
end if
beep 1
return (container of this_item)
exit repeat
end repeat
end tell
When I run this - it beeps four times, opens the folders 2006, Feb and Feb9 and the result is:
folder “temp” of folder “Desktop” of folder “old_bfindlay” of folder “Users” of startup disk of application “Finder”
So I am misunderstanding what this code is telling applescript to do. What I thought it would do would be to drill down to the Feb9 level, beep once and return the container value ‘Feb9’ - indicating it was on the first of the files that reside there.
The following works on my system (older AppleScript versions might choke on it). Run it to see if/how it does on yours:
on drill_down(a_folder)
tell application "Finder"
set file_list to files of a_folder
set folder_list to folders of a_folder
end tell
repeat with i in file_list
processDrilledDownFile(i)
-- relieve the script by handling the found files first,
-- before diving deeper into the recursion with the found folders,
-- as this will minimize occurences of a stack overflow
end repeat
repeat with i in folder_list
drill_down(i) -- diving into the recursion with the found folders here
end repeat
end drill_down
on processDrilledDownFile(a_file)
-- a_file is passed on to the processDrilledDownFile handler as a Finder file object
-- so it must be further processed starting from within a "tell application Finder" block
tell application "Finder"
beep
activate
reveal a_file
-- replace the 3 lines above with your own file processing code
end tell
end processDrilledDownFile
drill_down(choose folder)
HTH
Jan
Model: Alu-PowerBook 15" 1.25 GHz
AppleScript: 1.10
Browser: Safari 417.8
Operating System: Mac OS X (10.4)
Jan - beautiful code! Thanks - that worked - for the drill down part.
Now, in order to process the files, I have to pass the counter ‘i’ (ie the numeric value of it) to my process routine
repeat with i in file_list
processDrilledDownFile(i)
I need to change this to something like:
repeat with i in file_list
set counter to “numeric value of i”
processDrilledDownFile(i, counter)
However, I can’t see how to extract that value. The Applescript book I am working out of is Danny Goodmans version 2 - circa 1998, so it is outdated. The Finder ‘Dictionary’ in Script editor is useless for telling me how to do anything. I know what I want to do, I simply don’t have a reference that shows me the correct syntax of how to do it.
Jan - figured it out. I set counter to 1 before the repeat loop, then incremented it manually. Seems clunky to me - if there IS a way to extract the iteration value natively, I would rather use that.
Thanks so much for your help. We are fully up to speed now!!
Yes, the “repeat with i in a_list” call used to be very finicky in earlier Finder applescripting…
That’s why at the time Goodman and others recommended “repeat with i from 1 to length of a_list” and similar workarounds. But with current AppleScript versions simply declaring the filecounter variable as below does the job:
on drill_down(a_folder)
tell application "Finder"
set file_list to files of a_folder
set folder_list to folders of a_folder
end tell
set theCounter to 0 -- declare the filecounter variable here
repeat with i in file_list
set theCounter to theCounter + 1 -- increment the filecounter variable
processDrilledDownFile(i, theCounter) -- pass on the filecounter variable to the processDrilledDownFile handler
end repeat
repeat with i in folder_list
drill_down(i)
end repeat
end drill_down
on processDrilledDownFile(a_file, a_counter)
tell application "Finder"
beep
activate
display dialog (a_counter as string) giving up after 1
reveal a_file
-- replace the 4 lines above with your own file processing code
end tell
end processDrilledDownFile
drill_down(choose folder)
HTH,
Jan
Model: Alu-PowerBook 15" 1.25 GHz
AppleScript: 1.10
Browser: Safari 417.8
Operating System: Mac OS X (10.4)
Hi. Sorry to butt in here, but I don’t think these two assertions are quite correct. As far as I can recall, any sort of repeat involving a list has always been OK, even with the Finder. The dodgy area comes when you repeat through a Finder reference to multiple items. If you ‘get’ those items first, as in:
… then you’re working with a fixed, AppleScript list and there’s no problem. But if you apply the repeat directly to the Finder reference:
… then you could experience strange effects if what happens in the repeat either removes files from the folder or causes the Finder to list them in a different order internally. The repeat here cycles through the Finder-numbered files in the folder, not through the items in an AppleScript list. If there are, say, ten files in the folder, the repeat will be set up to iterate ten times. If the first iteration removes the first file, files 2 thru 10 of the folder will become files 1 thru 9. The next iteration will thus address the new file 2, which is the erstwhile file 3. So the original file 2 will be skipped. At some point, the repeat index will become greater than the number of files in the folder and you’ll get an error.
‘Repeat with i from 1 to length of a_list’ isn’t a “workaround” but a different way of indexing a repeat. In it, i sequentially adopts all the integer values from 1 to (length of a_list). With ‘repeat with i in a_list’, i sequentially adopts all the reference values from ‘item 1 of (value of a_list)’ to ‘item (length of a_list) of (value of a_list)’. This sometimes causes confusion in equality tests.
set a_list to {1, 2, 3, 4, 5}
repeat with i in a_list
if (i = 3) then beep
end repeat
Although the third item in the list is indeed 3, the value of i at that point is the reference ‘item 3 of {1, 2, 3, 4, 5}’, which isn’t the same as the integer value 3 and so no beep is sounded. This can be annoying but is, strictly speaking, correct behaviour. (We’re testing the contents of the variable i, not the contents of the reference.) However, it only affects equality tests. With non-equality tests, the references are resolved:
set a_list to {1, 2, 3, 4, 5}
repeat with i in a_list
if (i > 2 and i < 4) then beep
end repeat
However, you’d normally use the ‘contents’ operator here:
set a_list to {1, 2, 3, 4, 5}
repeat with i in a_list
if (i's contents = 3) then beep
end repeat
Thanks for the insight, Nigel, and for setting the record straight on my faulty “repeat with i in a_list” remarks. I knew I had often run into trouble with that type of repeat call in the past and that for some reason with more recent versions of AppleScript, the situation appeared to have improved, but I didn’t really understand why. I did check that the repeat calls in the scripts in this thread worked. But thanks to your explanation, now I know why, thanks again. I stand corrected…