Making AppleScript more reusable with the visitor pattern.

Hello.

Sometimes, you may think yourself obstructed when trying to write reusable code, because, the code that really makes it all work is in the very “meat” of what could have been reusable.

Then the visitor pattern may come in handy. Below is an illustration of it.

(*
	closestRootFolder:
	You can have several "roots" below the last one, say in a build-hierachy of c-source, or markdown/html.
	closestRootFolder traverses the directory structure upwards to the stopPath, and checks each folder on its way by the visitor you supplied.
	
	parameter: probePath
	Finds the closest root folder of a file. The closest root being some folder one or several folders up that satisifes some criteria.
	parameter: stopPath
	We supply a stopFolder, which is the heighest level, or last tier if you like, for this particular root. 
	Parameter: Visitor
	A script you supply with the handler "validator", that tells us if the current path satisifes the criteria for a root folder.

	Returns false, if no root where found, or the path to the closest root for the probepath supplied.
	
	
	An example visitor script for which I wrote this library for.
	The root folder I am looking for contains a folder named "m4", and a makefile.
	
	script visitor
		on validator(probePxPath)
			tell application id "sevs"
				if not (exists disk item (probePxPath & "/makefile")) then return false
				if not (exists disk item (probePxPath & "/m4")) then return false
			end tell
			return true
		end validator
	end script
	
*)

on closestRootFolder(probePath, stopPath, visitorScript)
	
	-- We start checking that our arguments actual has some value.
	if not hasContents(probePath) then error "isInADomain:  probePath can't be empty"
	if not hasContents(stopPath) then error "isInADomain:  posixPath can't be empty"
	if not class of visitorScript is script then error "isInADomain:  Visitor script needs to be a script "
	
	set probePxPath to UFS(probePath)
	set stopPxPath to UFS(stopPath)
	
	if stopPxPath is not in probePxPath then return false
	set containingFolder to pxpathExclFilename(probePxPath)
	repeat
		set testResult to visitorScript's validator(containingFolder)
		if testResult is true then
			return containingFolder
		else if containingFolder is stopPath then
			return false
		else
			-- we're iterating upwards
			set containingFolder to parentPxPathFolder(containingFolder)
		end if
	end repeat
	
	return true
end closestRootFolder

on hasContents(somePath)
	if class of somePath is list and somePath is {} then return false
	if class of somePath is text and somePath is "" then return false
	if somePath is null or somePath is missing value then return false
	return true
end hasContents

on UFS(whatever)
	local uxPth
	try
		if class of whatever is list then set whatever to whatever as text
		
		if class of whatever is not alias and class of whatever is not text then
			set uxPth to POSIX path of (whatever as alias)
			
		else if (class of whatever is alias) then
			set uxPth to POSIX path of whatever
		else
			if text 1 of whatever is "'" then ¬
				set whatever to text 2 thru -1 of whatever
			
			if text -1 of whatever is "'" then ¬
				set whatever to text 1 thru -2 of whatever
			
			if (offset of ":" in whatever) is not 0 then
				try
					set uxPth to POSIX path of (whatever as alias)
				on error e number n
					-- maybe it was ok as it was ..
					try
						(POSIX file whatever as alias)
						set uxPth to whatever
					on error e number n
						error e number n
					end try
				end try
			else
				(POSIX file whatever as alias)
				set uxPth to whatever
			end if
		end if
		return uxPth
	on error e number n
		return missing value
	end try
end UFS

to parentPxPathFolder(aPxPath)
	local tids, parFol
	set {tids, my text item delimiters} to {my text item delimiters, "/"}
	
	if (character (length of aPxPath) of aPxPath) = "/" then
		set parFol to text items 1 thru -3 of aPxPath as text
	else
		set parFol to text items 1 thru -2 of aPxPath as text
	end if
	set my text item delimiters to tids
	if parFol = "" then set parFol to "/"
	return parFol
end parentPxPathFolder

on pxpathExclFilename(pxPath)
	
	if not (class of pxPath is text and pxPath contains "/") then error "pxpathExclFilename: pxPath isn't a valid posixPath"
	if pxPath = "/" then error "pxpathExclFilename: rootfolder (\"/\") given as parameter"
	local tids, parFol
	set {tids, my text item delimiters} to {my text item delimiters, "/"}
	
	if (character (length of pxPath) of pxPath) = "/" then
		set parFol to text 1 thru -2 of pxPath
	else
		set parFol to text items 1 thru -2 of pxPath as text
	end if
	set my text item delimiters to tids
	if parFol = "" then set parFol to "/"
	return parFol
	
end pxpathExclFilename