Can a handler (subroutine) call itself?

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?

It might not be legal in every sense of the word… :slight_smile:

…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

drill_down({alias “Macintosh HD:Users:old_bfindlay:Desktop:temp:”})

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.

Jan figured that last one out.

Now just need to debug the script I have written.

Here is the code so far - I have a structure with the following folders:

      temp>2006>Jan>Jan1>many files
                             Jan3>many files
                             Jan 15>many files
                             file 1
                             file 2
                        feb>feb9

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

end drill_down

drill_down({alias “Macintosh HD:Users:old_bfindlay:Desktop:temp:”})

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.

…color me lost.

OK, lets simplify… :slight_smile:

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.

–on the home stretch!

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

Cheers!

Yes, the “repeat with i in a_list” call used to be very finicky in earlier Finder applescripting… :confused:

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

Nigel has correctly delineated every single hangup I’ve ever encountered with “repeat with anItem in aList…”

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… :slight_smile:

Jan