Find parent folder handler code share, plus a couple of curious ?s

I wanted a handler to return a folder a specified distance up the path hierarchy from a submitted path. But Applescript supports an annoying number of possible ways to specify a path, and I wanted it to handle all of them. It’s working fine, I’m posting it to share, but at the end I’ve got a question I came across while working on it, in case anyone would care to chime in. It’s not important, it seems to be working fine in testing, I’m just idly curious.

Also, if anyone has improvements or suggestions to add features, let me know.

The script:


use AppleScript version "2.4" -- Yosemite (10.10) or later
use scripting additions


-- TESTING VARIOUS TYPES

set aliasPath to path to downloads folder
tell application "Finder" to set folderObject to folder aliasPath
set fileObject to item 1 of folderObject
set HFStextPath to aliasPath as text
set POSIXtextPath to POSIX path of aliasPath
set POSIXfile to POSIX file POSIXtextPath

set stepsUp to 2

set aliasTest to step_up_path(aliasPath, stepsUp)
set folderObjectTest to step_up_path(folderObject, stepsUp)
set fileObjectTest to step_up_path(fileObject, stepsUp)
set HFStextTest to step_up_path(HFStextPath, stepsUp)
set POSIXtextTest to step_up_path(POSIXtextPath, stepsUp)
set POSIXfileTest to step_up_path(POSIXfile, stepsUp)


-- Accepts a file or folder reference submitted in any format - alias, Applescript file or folder object, POSIX file, or a POSIX or HFS path as text. Returns reference to higher folder in the same format in which the path was submitted. Second argument is the number of steps to traverse up the heirarchy.
on step_up_path(aPath, numberOfSteps)
	set pathClass to the class of aPath
	if pathClass is «class furl» then -- class of POSIX path
		set aPath to aPath as alias
	else if pathClass is text then
		try
			set aPath to alias aPath
			set textPathType to "HFS"
		on error
			try
				set aPath to (POSIX file aPath) as alias
				set textPathType to "POSIX"
			on error
				display dialog "Error: Text was input to the function \"step_up_path\", but the text could not be converted to a valid HFS or POSIX path."
				return
			end try
		end try
	end if
	set originalPath to aPath
	
	tell application "Finder"
		repeat numberOfSteps times
			try
				set aPath to the parent of aPath
			on error
				display dialog "Error: In the function \"step_up_path,\" the number of requested steps (" & numberOfSteps & ") was greater than the number of path elements in:" & return & originalPath as text
				return
			end try
		end repeat
		
		if pathClass is alias then
			set returnPath to aPath as alias
		else if pathClass is «class furl» then
			set returnPath to POSIX path of (aPath as alias)
		else if (pathClass is folder) or (pathClass is document file) then
			set returnPath to aPath
		else if pathClass is text then
			if textPathType is "HFS" then
				set returnPath to (aPath as alias) as text
			else if textPathType is "POSIX" then
				set returnPath to POSIX path of (aPath as alias)
			end if
		end if
	end tell
	return returnPath
	
	
end step_up_path

OK, two questions that came up writing this.

ONE

set aliasPath to path to downloads folder
set aliasClass to the class of aliasPath
set textPath to aliasPath as text

-- works
set convertClassExplicitly to textPath as alias

-- errors
set convertClassWithVariable to textPath as aliasClass

If you can store a class in a variable, why can’t you convert things to that class specifying it as a variable? Is there a way to do this? I guess it can be meta-scripted:

set aliasPath to path to downloads folder
set aliasClass to the class of aliasPath
set textPath to aliasPath as text

-- works
set convertClassExplicitly to textPath as alias

-- works
set convertClassWithVariable to run script "set convertClassWithVariable to \"" & textPath & "\" as " & aliasClass as text

But that always feels confusing and hack-y to me.

As you can see in my script, I just worked around it with a long “if…then” statement for all the classes.

The error is a compilation error, not a runtime error, so if you use a variable, the compiler has no way of knowing whether it contains a class. It’s just a rule of the compiler. Such rules are generally about flagging potential errors when scripts are written, rather than when they are run, which is arguably safer, if at times seemingly less convenient.

Hi t.spoon.

I’d be inclined to use TIDs on the path rather than getting the Finder to work its way up through the containers. But subjectively, it doesn’t seem to make any difference to the speed. You’ll notice my comment at the top of the script that it works as far back as Leopard too except for the two ‘use’ lines. These lines don’t compile in Leopard because ‘use’ was only introduced with Mavericks (10.9). But if the script’s compiled in Mojave and opened in Leopard, the only thing you’ll see of the two lines is the comment saying they don’t work. The ‘use’ instructions aren’t visible in Script Editor and don’t cause the script to error when it runs.


use AppleScript version "2.4" -- Yosemite (10.10) or later, but it works in Leopard (10.5) too (except for these two lines!)
use scripting additions


-- TESTING VARIOUS TYPES

set anAlias to (path to downloads folder)
set aFile to anAlias as «class furl»
set HFSPath to anAlias as text
tell application "Finder"
	set FinderFolder to folder HFSPath
	set FinderFile to file 1 of FinderFolder
end tell
tell application "System Events"
	set SystemEventsFolder to folder HFSPath
	set SystemEventsFile to file 1 of SystemEventsFolder -- System Events's 'file 1' may be different from the Finder's!
end tell
set POSIXPath to POSIX path of anAlias
set POSIXfile to POSIX file POSIXPath -- Should be the same as aFile

set stepsUp to 2

set aliasTest to step_up_path(anAlias, stepsUp)
set fileTest to step_up_path(aFile, stepsUp)
set FinderFolderTestTest to step_up_path(FinderFolder, stepsUp)
set FinderFileTest to step_up_path(FinderFile, stepsUp)
set SystemEventsFolderTest to step_up_path(SystemEventsFolder, stepsUp)
set SystemEventsFileTest to step_up_path(SystemEventsFile, stepsUp)
set HFSPathTest to step_up_path(HFSPath, stepsUp)
set POSIXPathTest to step_up_path(POSIXPath, stepsUp)
set POSIXfileTest to step_up_path(POSIXfile, stepsUp)


-- Accepts a file or folder reference submitted as an AppleScript alias or file, an HFS or POSIX path, or a Finder or System Events object. Returns reference to higher folder in the same format in which the path was submitted. Second argument is the number of steps to traverse up the heirarchy.
on step_up_path(pathOrSpecifier, numberOfSteps)
	set inputClass to the class of pathOrSpecifier
	try
		set pathString to pathOrSpecifier as text
		set isSystemEventsObject to false
	on error
		tell application "System Events" to set pathString to path of pathOrSpecifier
		set isSystemEventsObject to true
	end try
	if ((pathString ends with ":") or (pathString ends with "/")) then set pathString to text 1 thru -2 of pathString
	
	set astid to AppleScript's text item delimiters
	if (pathString begins with "/") then
		set AppleScript's text item delimiters to "/"
	else
		set AppleScript's text item delimiters to ":"
	end if
	try
		set newPath to (text 1 thru text item -(1 + numberOfSteps) of pathString) & AppleScript's text item delimiters
		if (newPath is "//") then set newPath to "/"
	on error
		set AppleScript's text item delimiters to astid
		display dialog "Error: In the function \"step_up_path,\" the number of requested steps (" & numberOfSteps & ") was greater than the number of container elements in:" & return & pathString
		return
	end try
	set AppleScript's text item delimiters to astid
	
	if (inputClass is text) then
		set output to newPath
	else if (inputClass is alias) then
		set output to newPath as alias
	else if (inputClass is «class furl») then
		set output to newPath as «class furl»
	else if (isSystemEventsObject) then
		tell application "System Events" to set output to disk item newPath
	else
		tell application "Finder" to set output to item newPath
	end if
	
	return output
	
end step_up_path

Thanks for the explanation, Shane.

Nigel - thanks for your version, especially pointing out that System Events file/folder objects need to be handled separately from Finder file/folder objects.

  • Tom.

I skimmed the two main offerings on the thread, and I think the salient points have been covered. Instead, I just wrote the script below, but haven’t tested it fully yet. I am hoping that it is capable of handling filepaths of any nature (posix including tilde abbreviated filepaths, HFS, alias objects, and Finder filepaths). It returns the result as a posix path.

property sys : application "System Events"


to ascend thru filepath as text by levels as integer
		local filepath, levels
		
		if levels < 1 then return the filepath's POSIX path
		sys's alias named filepath as alias as text
		[result, "::"] as text as alias
		
		ascend thru the result by (levels - 1)
end ascend

ascend thru "~/Downloads" by 2 --> "/Users/"

I haven’t evaluated efficiency in practice, and I’m mindful there are a lot of coercions happening in these few lines of code, which may impact on performance. Using a recursive handler puts a theoretical stack limit on the number of levels it could ascend before crapping out; but I don’t imagine this being an issue in practice.

I’ll do some further testing/debugging over the next week. This is a good thread, so thank you for starting it. I’d not considered this operational task before, but I can see it would a useful one to add to my filesystem library, once I’ve confirmed it is reliable. So do feel free to comment, improve it, or break it.

It seems to work with these. But it can’t handle paths and AS file specifiers to items which don’t exist and, although it uses System Events, it can’t handle System Events references!

The idea was to return the result in the same form as the input. But ignoring that, the same capabilities as your handler can be obtained without resorting to recursion or undesirable code like:

[ and ] aren’t official AppleScript symbols, some schools of thought reckon the result variable shouldn’t be used in code in case other lines are subsequently added between it and the line that’s supposed to set it, and coercing a list to text without explicitly setting the TIDs first is generally considered sloppy. :wink:

But the following still uses the almost forgotten black art of using multiple colons at the ends of HFS paths to get aliases to container folders:


to ascend thru filepath by levels
	tell application "System Events" to set filepath to (alias (filepath as text))'s path
	
	repeat levels times
		set filepath to ((filepath as text) & "::") as alias
	end repeat
	
	return filepath's POSIX path
end ascend

ascend thru (path to downloads folder) by 2 --> "/Users/"

That’s unsurprising about non-existent paths; but surprising to hear about SE references, so thank you for finding that out.

Noted. I think that is a reasonable expectation, and I’ll fix that.

That’s fine, I’m aware of these points, and I believe you may have mentioned it in the past to me on the Keyboard Maestro forum. I don’t disagree with any particular point, but I feel familiar enough with these facets to use them reliably and effectively. But it is good that you point these things out, and I should at least explain a bit about my choices (which I am perfectly open to being debunked):

▸ I use that “undesirable” syntax because I find it makes my scripts a lot easier for me to read and debug. Expressions like: set filepath to ((filepath as text) & “::”) as alias; are, of course, more widely accepted as good, standard AppleScript form, but I’ve always found it extremely difficult to read. It might be a dyslexia issue, it might be a neurosis specific to me, or it might be something I simply perceive to be less “beautiful” to look at than my form (which you find less beautiful; eye, beholder, etc.)

▸ I think “official” is becoming less and less meaningful as time goes on to think in those terms with AppleScript, as many officially deprecated features still have viable features and functionality that have persisted for years since being deprecated (wasn’t info for deprecated about ten years ago, but still returns really performant results?). I imagine you already know the history of the square brackets (à la linked lists), so we don’t necessarily need to go through it unless anyone is interested to know. But, they do remain viable notation for list objects and, for me, much easier to read, especially when one intermixes their use with curly braces. One does need to be aware of when the different notations will have a functional impact on script operation, which I don’t doubt you’re familiar with, and can be useful to have artefacts of a class type persist that let AppleScript do things it shouldn’t really be able to, but will probably continue to do until its death.

▸ Specifically, I employ the square brackets because they remain unaffected by TIDs. Thus, I don’t have to care in this instance what the TIDs are set to, because it won’t affect the coercion. Otherwise, yes, you are obviously quite right about the pitfalls of potentially forgetting to set TIDs.

▸ You’re not the first to highlight the school of thought regarding the use of the result property, and it is important to point out. It’s not a school of thought I particularly feel I need to worry about for myself, as I suppose I’m already conditioned with some good habits around examining things above and below any line of code I edit (for example), that it’s something I do without pause.

Thank you for highlighting these points, though, as it does for me to stop and think as I’m writing my rationale about whether I need to re-evaluate a particular habit; and other readers will be glad to know that my “special” style of scripting is not necessarily one to emulate without knowing about how it can change a script beyond just its appearance. (These, and a few other quirks specific to my AppleScripting style t does mean I can spot instantly when a script on the internet has been spawned from one of mine, which is nice to come by.)

Thankfully, I didn’t forget about this art, as it’s precisely the technique my script utilises. Part of me wondered why it didn’t appear in the OP’s script or your edited version, but as you’ve pointed out, my handler depends on a fielpath existing.

I noted your remark above about my resorting to recursion, when, as you demonstrate, the same can be achieved through iteration. I didn’t see it as a resort, but your reputation precedes you for having a keen sense about all things recursive, iterative, and generally algorithmic, as well as code optimisation. So I would value your take on assessing one method vs the other in this case in terms of function, operation, efficiciency, and potential pitfalls.

I can sympathise with that. :slight_smile: When I was learning ASObjC a couple of years ago, I had to develop my own style just to stay interested in the subject. Otherwise the succession of this’s thats and that’s theOthers just came across like a game of Mornington Crescent. However, I was careful to keep my style within what I could determine to be good practice and its purpose was — as you yourself say — for clarity in scripts rather than for mere eccentricity or smartarsedness.

I’m always aware — especially since I got landed with MacScripter’s “Moderator” tag — that scripting fora are frequented both by people who know what they’re doing and people who don’t. It’s up to the former group to be as helpful as possible to the latter, providing clear code solutions where needed, explaining how and why they work (not just “Try this:”!), and avoiding any known dodgy practices which may be taken at face value and be perpetuated later here, elsewhere on the Web, or even in people’s workplaces. It can be a right pain continually having to correct the same old errors which have been copied from the Web in good faith and a difficult line to steer, when you don’t know who the posters are, between criticising their code and putting them off scripting altogether.

While none of us gets it right all the time, it’s best to pass on the best code we can in a public arena. What we do on our own machines, though, is another matter. :wink:

I agree with the entirety of what you wrote in your last reply.

That gives me something to ponder on. Is there a an official “good coding practices” for AppleScript published online that I can reference ?