File/Folder Duality of Applications: How to treat like either?

I was afraid of that. It’ll be an even bigger info.plist file then!

My scripts have a way of blowing up like that. It would be a lot smaller but for the capability of modifying the file associations it manages. There’s a lot of logic that is really similar, but I rushed through it and didn’t bother to find a more clever way to represent things.

Hi scriptim,

Maybe you just need to add it to the Applications folder. I think that might have done it the first time I got it in the list.

gl,
kel

No, I think you need to enter every extension, then I just drop it into the Applications folder.

Maybe you can just copy the list of extensions from somewhere like other apps.

gl,
kel

Already have a script running: list of main folders + “ls -R” + hash table + merge sort, all in AS (except for the ls action)!!

output it all with the appropriate tags and I can just paste it into the info.plist file. then I can save my precious 10 seconds when I want to open things!

Hi scriptim,

You’re forgetting the priceless fun you’ve had! :smiley:

Have a good day,
kel

Hello scriptim.

Nice applet you got there. :slight_smile:

The * just won’t do. I think you’ll have to specify public.item (this will work with everything that can invoke the open with menu).

You can read about it here and here.

Edit

Actually, I recommend you copy everything from the key: CFBundleDocumentTypes and the full array below it from the info.plist of TextWrangler. Remove the things regarding icons and such, and try to specify public.item as the sole member of the LSItemContentTypes array. (Keep the CFBundleTypeRole as Editor, and specify CFBundleTypeName as “scriptims filetype”, I would keep the CFBundleTypeIconFile, but specifiy the applet icon (applet.icns), but copy it into the Resources folder of your applets Contents folder as well ).

HTH and good luck! :slight_smile:

Good idea. it was actually enough to just include the mega-list of file extensions I got by parsing recursive ls commands in my main document folders. Then I re-registered my applet with launch services using

on run
	set AppName to choose file with prompt "Choose application to re-register with launch services" of type {"app"} default location (path to applications folder)
	
	set AppName to AppName as text
	if text item -1 of AppName is ":" then repeat until text item -1 of AppName is not ":"
		set AppName to (text items 1 through -2 of AppName) as text
	end repeat
	
	set Scpt to "/System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/LaunchServices.framework/Versions/A/Support/lsregister -f " & quoted form of POSIX path of AppName
	do shell script Scpt
end run

And I was off to the races! (I never get a chance to say that…)

Now for every file on my system I see “¢ Open With” as the top item of the open with menu, and if there’re no applications associated with the filetype then it defaults to my applet!

Now for the next step:

In my readings (I had found what you linked to, McUsrII, in my own searching but it scared me so I asked here instead :slight_smile: I found that you can also modify the icon that OS X uses for file types, and that the icon and filetypes are specified i the info.plist file in a similar way to what I’m doing already. Also, there’s no lack of example info.plist files to look at in one’s applications folder.

So my plan now is to have my applet maintain icon associations as well, so that filetypes that have an association through my applet (and only my applet, so this applies to previously unassociated filetypes) show the correct icon for whatever program they’re associated with. This should be as simple as adding some lines to the info.plist file that point to the icon of the association application for the particular file type. Or I’ll just get distracted and start doing something else…

Thanks again for your help guys! I wouldn’t have thought of this anytime soon on my own, and likely would have broken my system! Thanks Shane Stanley for the initial indication of my evil mis-doings!

Hi McUsr,

What you’re saying is that if you do this, then you don’t need to enter every extension? That sounds right, because from the developer docs:

But, would it add the application to the ‘Open with’ list?

Have a good day,
kel

Hello kel.

You’ll have to have a look at TextWrangler’s info.plist, then you’ll realize, that it is the LSItemContentTypes that makes textwrangler pop up as an app a file can be open with. My idea, which is untested, is that the public.item is the parent or containing public.png for instance.

Anyways, scriptim included the extensions he found in his folders, and filled out a huge array of CFBundleTypeExtensions I guess, so I’ll leave the question whether public.item work or not, out in the open. :wink:

@scriptim:
The key for the icon, is the CFBundleTypeIconFile, you’ll see its usage in the info.plist file of TextWrangler (for having your own app icon associated with the files).

Tomorrow. :wink:

Hi McUsr,

I’ll test it out and see if it works. From the developer docs, it seems that all you need to do is add the NSExportableTypes key with the same value public.item. Good find if it works. I haven’t seen this in my research.

Edited: the one thing that makes it a little bit harder is that you need to add it to the plist with text instead of with Xcode’s plist editor, because according to the docs theres no equivalent Xcode key.

Edited: oops, there is an Xcode equivalent.

Later,
kel

No, it didn’t work unless I’m missing something. Here’s the modified droplet’s info.plist:

That would have been great if it worked. I learned a lot though.

Thanks anyway and have a good day! :slight_smile:
kel

Hello kel.

If you did this after you had saved your app, then it should have registered the first time, before you edited the info.plist file. Maybe this doesn’t work after all, but still you should try to increase the short version string (CFBundleShortVersionString) and double click the droplet afterwards, to force it to register.

I don’t think you can mix LSItemContentTypes and CFBundleTypeExtensions and, maybe it helps using public.data, instead of public.item (maybe).

Personally, I am thinking of just carboncoping TextWranglers specifications. :slight_smile:

I hope it works!

Hi McUsr,

When I drop the application into the Applications folder, it automatically, I think, registers the app. But, for overkill, I quit Finder.

According to the developer documents, the LSItemContentTypes overrides the extension thing. I’ll try it with public.data and see what happens. Although, I don’t think it will work, because in my research, nobody has ever mentioned this. Who knows. Maybe you’re onto a secret that nobody knows. :slight_smile: Ain’t no big thing bruddah Tom.

Thanks,
kel

No. I’ve tried all combinations of settings and it still doesn’t show in the Open with list.

Nice try though.

Thanks,
kel

Hi McUsr,

Think I’m onto something after looking at TexWranglers info.plist. I see what you were trying to write. Why is it in the ‘Open with’ list? Although, it does have a big Document Content Type key.

Later,
kel

what I was thinking was that you might need to add exported type utis. Nice puzzle.

Yeah, then under equivalent types they have the extensions. I wonder why they did it this way instead of just adding the extensions in CFBundleTypeExtensions. Maybe to distinguish between the different types for their own documents. Starting to see the light I think. :smiley:

I.e. scriptim, it is probably ok to add the extensions as we did, as long as we’re not creating documents. Just adding to the ‘Open with’ list. Conjecture.

Thanks a lot,
kel

So here’s version 2 of the ‘open with’ applet/droplet.

The fancy new feature is that you can drop folders on the applet (or select them from the menu) and the folders will be scanned and all extensions of the files contained will be added to the ‘open with’ applet’s info.plist file, and the applet reregistered with launch services. This takes the work out of getting it on the “open with” menu for all the file types you might want to use it for!

The scanning is pretty quick, a has table is used to maintain a list of extensions free of duplicates, and it get’s sorted before being reinserted into the info.plist file.

The script follows.

Thanks again for all the help guys.

Special thanks to DJ Bazzie Wazzie for the AS hash table implementation, Nigel Garvey for the merge sort, and kel1, McUsrII, and Shane Stanley for their help in this thread.


property SampleList : {{path:"Applications:gedit.app", name:"gedit", BundleID:"org.gnome.gedit", TypeExtList:{"log", "script"}}}

property DefaultAppListFile : "DefaultApps.b"
property SummaryFile : "File Association Summary.txt"

property HTableSize : 1000

property TheList : {}

on open theFiles
	local FileExt, FileName, FoundAppList, OpenWithAppList, PickAppList, FileList, FolderList
	
	set FileList to {}
	set FolderList to {}
	
	repeat with aFile in theFiles
		if text item -1 of (aFile as text) is ":" then
			set end of FolderList to aFile
		else
			set end of FileList to aFile
		end if
	end repeat
	
	if (count of FolderList) > 0 then
		GetExtsFromFolders(FolderList)
	else
		GetList()
		
		repeat with aFile in FileList
			tell application "System Events"
				set FileExt to name extension of aFile
				set FileName to displayed name of aFile
			end tell
			set FoundAppList to {}
			set OpenWithAppList to {}
			repeat with AnApp in TheList
				if TypeExtList of AnApp contains FileExt then set end of FoundAppList to AnApp
			end repeat
			if (count of FoundAppList) = 1 then
				set OpenWithAppList to FoundAppList
			else if (count of FoundAppList) > 1 then
				set PickAppList to {}
				repeat with AnApp in FoundAppList
					set end of PickAppList to name of AnApp
				end repeat
				set PickAppList to choose from list PickAppList with title "Open " & FileName & " using:" default items {beginning of PickAppList} with multiple selections allowed without empty selection allowed
				if PickAppList is false then return 0
				repeat with PickedAppName in PickAppList
					repeat with AnApp in TheList
						if PickedAppName as string is (name of AnApp) as string then
							set end of OpenWithAppList to AnApp
							exit repeat
						end if
					end repeat
				end repeat
			else
				set end of OpenWithAppList to SelectDefaultApp(missing value, {FileExt})
				if end of OpenWithAppList is false then return 0
			end if
			repeat with AnApp in OpenWithAppList
				if BundleID of AnApp is not missing value then
					tell application "Finder" to open aFile using application file id (BundleID of AnApp)
				else
					tell application (path of AnApp)
						activate
						open aFile
					end tell
				end if
			end repeat
		end repeat
	end if
end open

on run
	local ActionList, AddExt, RemExt, RemApp, OpenFiles, ViewSummary, UpdateInfoPlist
	local TmpList, TmpStr, TmpButton
	set AddExt to "Add File Type Association"
	set RemExt to "Remove File Type Association"
	set RemApp to "Remove Application Association"
	set OpenFiles to "Open File(s)"
	set ViewSummary to "View File Type Association Summary"
	set UpdateInfoPlist to "Add this applet to more files' 'Open with...' menu"
	
	repeat
		GetList()
		
		if (count of TheList) > 0 then
			set ActionList to {AddExt, RemExt, RemApp, ViewSummary, UpdateInfoPlist, OpenFiles}
		else
			set ActionList to {AddExt, UpdateInfoPlist, OpenFiles}
		end if
		
		set TheAction to choose from list ActionList with prompt "What would you like to do?" with title "Open With..." default items {AddExt} without multiple selections allowed and empty selection allowed
		if TheAction is false then return 0
		set TheAction to beginning of TheAction
		
		if TheAction is AddExt then
			set {TmpStr, TmpButton} to {text returned, button returned} of (display dialog "Enter file extension(s) to add association for:" & return & return & "(separate multiple extensions with spaces)" default answer "" with icon note)
			if TmpButton is "Cancel" then return 0
			set TmpStr to parseLine(TmpStr, space)
			set TmpList to GetTmpList(false)
			set NewApp to "Select new application"
			if (count of TmpList) > 0 then
				set beginning of TmpList to NewApp
				set TmpList to choose from list TmpList with prompt "Add file association(s) to which application(s)?" default items {beginning of TmpList} with multiple selections allowed and empty selection allowed
				if TmpList is false then return 0
			else
				set TmpList to {NewApp}
			end if
			repeat with AnApp in TmpList
				if AnApp as string is NewApp as string then
					SelectDefaultApp(missing value, TmpStr)
				else
					SelectDefaultApp(AnApp, TmpStr)
				end if
			end repeat
		else if TheAction is RemExt then
			set TmpList to GetTmpList(true)
			if (count of TmpList) > 1 then
				set TmpStr to choose from list TmpList with prompt "Which file type extension would you like to remove?" default items {beginning of TmpList} without multiple selections allowed and empty selection allowed
				if TmpStr is false then return 0
				set TmpStr to beginning of TmpStr
			else
				set TmpStr to beginning of TmpList
			end if
			set TmpList to {}
			repeat with AnApp in TheList
				if TypeExtList of AnApp contains TmpStr then set end of TmpList to name of AnApp
			end repeat
			if (count of TmpList) > 1 then
				sort(TmpList, 1, -1, {})
				set TmpList to choose from list TmpList with prompt "Remove " & TmpStr & " association from which applications?" default items {beginning of TmpList} with multiple selections allowed without empty selection allowed
				if TmpList is false then return 0
			end if
			repeat with ByeApp in TmpList
				repeat with i from 1 to count of TheList
					if name of (item i of TheList) as string is ByeApp as string then
						if (count of TypeExtList of item i of TheList) > 1 then
							set TypeExtList of item i of TheList to RemoveFromList(TypeExtList of item i of TheList, {TmpStr})
						else
							set TheList to RemoveFromList(TheList, {item i of TheList})
						end if
						exit repeat
					end if
				end repeat
			end repeat
			SaveList()
		else if TheAction is RemApp then
			set TmpList to GetTmpList(false)
			if (count of TmpList) > 1 then
				set TmpStr to choose from list TmpList with prompt "Which application would you like to remove association(s) for?" default items {beginning of TmpList} without multiple selections allowed and empty selection allowed
				if TmpStr is false then return 0
				set TmpStr to beginning of TmpStr
			else
				set TmpStr to beginning of TmpList
			end if
			repeat with i from 1 to count of TheList
				if name of item i of TheList is TmpStr then
					if (count of TypeExtList of item i of TheList) > 1 then
						sort(TypeExtList of item i of TheList, 1, -1, {})
						set TmpList to choose from list TypeExtList of item i of TheList with prompt "Remove which file type associations from " & name of item i of TheList & "?" default items {beginning of TmpList} with multiple selections allowed without empty selection allowed
						if TmpList is false then return 0
						if (count of TmpList) < (count of TypeExtList of item i of TheList) then
							set TypeExtList of item i of TheList to RemoveFromList(TypeExtList of item i of TheList, TmpList)
						else
							set TheList to RemoveFromList(TheList, {item i of TheList})
						end if
					else
						set TheList to RemoveFromList(TheList, {item i of TheList})
					end if
					exit repeat
				end if
			end repeat
			SaveList()
		else if TheAction is ViewSummary then
			set TmpStr to (((path to me) as text) & "Contents:Resources:" & SummaryFile)
			tell application "Finder"
				activate
				reveal TmpStr
			end tell
			return 0
		else if TheAction is UpdateInfoPlist then
			GetExtsFromFolders(choose folder with prompt "Select one or more folders that will be scanned, and this applet will be added to the 'Open with...' menu for all filetypes within" with multiple selections allowed)
			return 0
		else if TheAction is OpenFiles then
			open (choose file with multiple selections allowed)
			return 0
		end if
		
	end repeat
end run

on OpenInfoFile(InfoPlistFile)
	
	if InfoPlistFile is not missing value then
		
		set InfoRef to open for access (InfoPlistFile as text)
		
		set InfoContents to read InfoRef
		set InfoContents to paragraphs of InfoContents
		set NewInfoContents to {}
		set EndInfoContents to {}
		set ExtList to {}
		set SectionNumber to 1
		repeat with aLine in InfoContents
			if SectionNumber is 1 then
				set end of NewInfoContents to aLine as string
				if (aLine as string) contains "CFBundleTypeExtensions" then
					set SectionNumber to SectionNumber + 1
				end if
			else if SectionNumber is 2 then
				if (aLine as string) contains "<string>" then
					set end of ExtList to beginning of parseLine(end of parseLine(aLine as text, "<string>"), "</string>")
				else if (aLine as string) contains "</array>" then
					set end of EndInfoContents to aLine as string
					set SectionNumber to SectionNumber + 1
				else
					set end of NewInfoContents to aLine as string
				end if
			else
				set end of EndInfoContents to aLine as string
			end if
		end repeat
		
		close access InfoRef
		
		return {NewInfoContents, EndInfoContents, ExtList}
	end if
end OpenInfoFile

on SaveInfoFile(InfoPlistFile, NewInfoContents, EndInfoContents, ExtList)
	
	if InfoPlistFile is not missing value then
		
		set NewInfoFileName to (InfoPlistFile as text) & ".tmp" as text
		set NewInfoRef to open for access NewInfoFileName with write permission
		set eof of NewInfoRef to 0
		
		repeat with i from 1 to count of NewInfoContents
			if i > 1 then write return to NewInfoRef
			write item i of NewInfoContents to NewInfoRef
		end repeat
		
		repeat with i in ExtList
			write ((return & tab & tab & tab & tab & "<string>" & i as text) & "</string>") as text to NewInfoRef
		end repeat
		
		repeat with i in EndInfoContents
			write return & i as text to NewInfoRef
		end repeat
		
		close access NewInfoRef
		
		set NewInfoFile to NewInfoFileName as alias
		
		set Scpt to "cp " & quoted form of POSIX path of InfoPlistFile & space & quoted form of (POSIX path of InfoPlistFile & ".bak")
		
		try
			do shell script Scpt --with administrator privileges
			
			set Scpt to "mv " & quoted form of POSIX path of NewInfoFile & space & quoted form of POSIX path of InfoPlistFile
			
			do shell script Scpt --with administrator privileges
			
			set Scpt to "/System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/LaunchServices.framework/Versions/A/Support/lsregister -f " & quoted form of POSIX path of ((text items 1 through -2 of ((path to me) as text)) as text)
			do shell script Scpt --with administrator privileges
		on error
			display alert "Failed to modify Info.plist file" as critical
			return 0
		end try
		
	end if
end SaveInfoFile

on GetExtsFromFolders(FolderList)
	
	if FolderList is not false then
		set count1 to count of FolderList
		
		SetProgress({message:"Adding file type information..."})
		
		--tell application "ASObjC Runner"
		--	activate
		--	reset progress
		--	set properties of progress window to {bar two visible:true, max value:count1, message:"Getting folder contents", button visible:true}
		--	show progress
		--end tell
		
		set InfoPlistFile to missing value
		
		try
			set InfoPlistFileName to (((path to me) as text) & "Contents:Info.plist") as text
			set InfoPlistFile to InfoPlistFileName as alias
		on error
			display alert "Couldn't get Info.plist file" as critical
			return false
		end try
		
		set {TopInfoContents, BottomInfoContents, ExtList} to OpenInfoFile(InfoPlistFile)
		
		set FullList to {}
		
		repeat count of ExtList times
			set end of FullList to true
		end repeat
		
		--set ExtList to {}
		--set hTable to my hashTable's newInstance()'s init()
		set hTable to my hashTable's newInstance()'s initWithKeysAndValues(ExtList, FullList)
		
		set i to 1
		repeat with aFolder in FolderList
			--set FullList to paragraphs of (do shell script "cd " & quoted form of POSIX path of aFolder & ";ls -R")
			set scr to "ls -R " & quoted form of POSIX path of aFolder & " | awk '
		/:$/&&f{s=$0;f=0}
		/:$/&&!f{sub(/:$/,\"\");s=$0;f=1;next}
		NF&&f{ print s\"/\"$0 }'"
			set FullList to paragraphs of (do shell script scr)
			set count2 to count of FullList
			SetProgress({max:count2})
			--tell application "ASObjC Runner"
			--	set properties of progress window to {max value two:count2, current value:i, detail:"folder " & i & " of " & count1}
			--end tell
			set j to 1
			repeat with aLine in FullList
				SetProgress({current:j, detail:"File " & j & " of " & count2 & " in folder " & i & " of " & count1})
				--tell application "ASObjC Runner"
				--	set properties of progress window to {current value two:j, detail two:"file " & j & " of " & count2}
				--	if button was pressed of progress window then
				--		hide progress
				--		return 0
				--	end if
				--end tell
				if aLine as string is not "" and (aLine as string) contains "." then
					set TheExt to replaceString(replaceString(aLine, "//", "/"), "/", ":")
					if text item 1 of TheExt is ":" then repeat until text item 1 of TheExt is not ":"
						set TheExt to (text items 2 through -1 of TheExt) as text
					end repeat
					try
						set TheExt to TheExt as alias
					end try
					if class of TheExt is alias then
						tell application "System Events"
							set TheExt to name extension of TheExt
						end tell
						if TheExt is not "" and TheExt is not missing value then
							hTable's setValueforKey(TheExt, true)
						end if
					end if
					--set TheExt to (end of parseLine(beginning of parseLine(beginning of parseLine(aLine, ":"), "/"), ".")) as string
					--if ExtList does not contain TheExt then set end of ExtList to TheExt
				end if
				set j to j + 1
			end repeat
			
			set i to i + 1
		end repeat
		
		SetProgress({message:"Finishing...", current:-1})
		--tell application "ASObjC Runner"
		--	reset progress
		--	set properties of progress window to {bar two visible:false, button visible:false, indeterminate:true, message:"Updating info.plist"}
		--end tell
		set ExtList to hTable's keys()
		sort(ExtList, 1, -1, {})
		
		SaveInfoFile(InfoPlistFile, TopInfoContents, BottomInfoContents, ExtList)
		
		--set OutputFile to (DesktopPath & "output.txt") as text
		--
		--set OutputFile to open for access OutputFile with write permission
		--set eof of OutputFile to 0
		--
		--set count1 to count of ExtList
		--tell application "ASObjC Runner"
		--	reset progress
		--	set properties of progress window to {bar two visible:false, button visible:true, max value:count1, current value:1, message:"writing output"}
		--end tell
		--set ExtList to hTable's keys()
		--sort(ExtList, 1, -1, {})
		--set i to 1
		--repeat with TheExt in ExtList
		--	tell application "ASObjC Runner"
		--		set properties of progress window to {current value:i, detail:"line " & i & " of " & count1}
		--		if button was pressed of progress window then
		--			close access OutputFile
		--			hide progress
		--			return 0
		--		end if
		--	end tell
		--	write ("<string>" & TheExt & "</string>" & return) as text to OutputFile
		--	set i to i + 1
		--end repeat
		
		--tell application "ASObjC Runner"
		--	hide progress
		--end tell
		
		--close access OutputFile
	end if
end GetExtsFromFolders


on GetList()
	local ListPath, ListFile
	set ListPath to (((path to me) as text) & "Contents:Resources:" & DefaultAppListFile)
	set ListFile to open for access ListPath -- with write permission
	--set eof of ListFile to 0
	--write SampleList to ListFile as list
	set TheList to {}
	try
		set TheList to read ListFile from 0 as list
	end try
	close access ListFile
end GetList

on SaveList()
	local ListPath, ListFile
	set ListPath to (((path to me) as text) & "Contents:Resources:" & DefaultAppListFile)
	set ListFile to open for access ListPath with write permission
	set eof of ListFile to 0
	write TheList to ListFile as list
	close access ListFile
	
	set ListPath to (((path to me) as text) & "Contents:Resources:" & SummaryFile)
	set ListFile to open for access ListPath with write permission
	set eof of ListFile to 0
	if (count of TheList) > 0 then
		repeat with AnApp in TheList
			write (name of AnApp & " (" & name of AnApp & ", located at " & path of AnApp & ")" & return & "File types: ") as text to ListFile
			repeat with i from 1 to count of TypeExtList of AnApp
				write (item i of TypeExtList of AnApp) as text to ListFile
				if i < (count of TypeExtList of AnApp) then write ", " as text to ListFile
			end repeat
			write (return & return) as text to ListFile
		end repeat
	else
		write "There are no file associations in effect." to ListFile
	end if
	close access ListFile
end SaveList

on SelectDefaultApp(NewAppName, FileExts)
	local SelectedApp, AppPath, AppName, AppBundleID, NewApp, ListModified
	
	if NewAppName is missing value then
		set SelectNewApp to true
		set SelectedApp to choose file with prompt "Select application to add file association to" default location (path to applications folder) of type {"app"} without multiple selections allowed
		if SelectedApp is false then return false
		
		set AppPath to SelectedApp as text
		if text item -1 of AppPath is ":" then repeat until text item -1 of AppPath is not ":"
			set AppPath to (text items 1 through -2 of AppPath) as text
		end repeat
	else
		set SelectNewApp to false
	end if
	
	repeat with AnApp in TheList
		if (SelectNewApp and AppPath is path of AnApp) or (not SelectNewApp and NewAppName as string is name of AnApp as string) then
			set ListModified to false
			repeat with AnExt in FileExts
				if TypeExtList of AnApp does not contain AnExt then
					set end of TypeExtList of AnApp to AnExt
					set ListModified to true
				end if
			end repeat
			if ListModified then
				SaveList()
				return AnApp
			end if
		end if
	end repeat
	
	if SelectNewApp then
		tell application "System Events" to set AppName to displayed name of SelectedApp
		set AppBundleID to missing value
		try
			tell application "Finder" to set AppBundleID to id of application file AppPath
		end try
		set NewApp to {path:AppPath, name:AppName, BundleID:AppBundleID, TypeExtList:FileExts}
		set end of TheList to NewApp
		SaveList()
		
		return NewApp
	end if
end SelectDefaultApp

on RemoveFromList(OldList, RemItemList)
	local NewList
	set NewList to {}
	if class of beginning of RemItemList is record then
		repeat with i from 1 to count of OldList
			set RemItem to false
			repeat with j from 1 to count of RemItemList
				if name of item i of OldList is name of item j of RemItemList then
					set RemItem to true
					exit repeat
				end if
			end repeat
			if not RemItem then set end of NewList to item i of OldList
		end repeat
	else
		repeat with i from 1 to count of OldList
			if item i of OldList is not in RemItemList then set end of NewList to item i of OldList
		end repeat
	end if
	return NewList
end RemoveFromList

on GetTmpList(MakeExtList)
	local TmpList, Tmp
	
	set TmpList to {}
	if not MakeExtList then
		repeat with AnApp in TheList
			set end of TmpList to name of AnApp
		end repeat
	else
		repeat with AnApp in TheList
			repeat with AnExt in TypeExtList of AnApp
				if TmpList does not contain AnExt as string then set end of TmpList to AnExt as string
			end repeat
		end repeat
		
	end if
	sort(TmpList, 1, -1, {})
	return TmpList
end GetTmpList

on SetProgress(P)
	try
		set progress completed steps to current of P
	end try
	try
		set progress total steps to max of P
	end try
	try
		set progress description to message of P
	end try
	try
		set progress additional description to detail of P
	end try
end SetProgress

on parseLine(theLine, delimiter)
	-- This came from Nigel Garvey
	
	set astid to AppleScript's text item delimiters
	set AppleScript's text item delimiters to {delimiter}
	set theTextItems to theLine's text items
	set AppleScript's text item delimiters to astid
	
	repeat with i from 1 to (count theTextItems)
		if (item i of theTextItems is "") then set item i of theTextItems to missing value
	end repeat
	
	return theTextItems's every text
end parseLine

--c--   replaceString(theText, oldString, newString)
--d--   Case-sensitive find and replace of all occurrences.
--a--   theText : string -- the string to search
--a--   oldString : string -- the find string
--a--   newString : string -- the replacement string
--r--   string
--x--   replaceString("Hello hello", "hello", "Bye") --> "Hello Bye"
--u--   ljr (http://applescript.bratis-lover.net/library/string/)
on replaceString(theText, oldString, newString)
	local astid, theText, oldString, newString, lst
	set astid to AppleScript's text item delimiters
	try
		considering case
			set AppleScript's text item delimiters to oldString
			set lst to every text item of theText
			set AppleScript's text item delimiters to newString
			set theText to lst as string
		end considering
		set AppleScript's text item delimiters to astid
		return theText
	on error eMsg number eNum
		set AppleScript's text item delimiters to astid
		error "Can't replaceString: " & eMsg number eNum
	end try
end replaceString

on CustomMergeSort(ListToSort, l, r, customiser) -- Sort items l thru r of theList.
	script o
		property comparer : me
		property slave : me
		
		property lst : ListToSort
		property rangeB : missing value
		
		on msrt(l, r)
			-- Get indices for the end of the left half of this range and the beginning of the right.
			set m1 to (l + r) div 2
			set m2 to m1 + 1
			
			sortHalf(l, m1) -- Sort the left half.
			sortHalf(m2, r) -- Sort the right  half.
			
			-- Extract a copy of the whole range.
			set rangeB to items l thru r of my lst
			slave's extract(l, r)
			
			-- Merge the already sorted halves of the extract back into the original: for each slot
			-- in the original, compare the lowest unassigned item from the left half of the extract
			-- with that from the right half and assign the lower of the two to the slot.
			
			
			-- Indices relative to the extract.			
			set i to 1 -- Beginning of left half.
			set endLeft to m2 - l -- End of left half.
			set j to endLeft + 1 -- Beginning of right half. 
			set endRight to r - l + 1 -- End of right half.
			
			-- Get the first item from the left and right halves of the extract.
			set lv to beginning of my rangeB
			set rv to item j of my rangeB
			repeat with k from l to r
				if (comparer's isGreater(lv, rv)) then
					-- The right value's less than the left. Assign it to this slot in the original list.
					set item k of my lst to rv
					slave's merge(j, k)
					-- Get the next right value. If none, assign the remaining left values to the remaining slots and exit this repeat and recursion level.
					if (j < endRight) then
						set j to j + 1
						set rv to item j of my rangeB
					else
						repeat with k from (k + 1) to r
							set item k of my lst to item i of my rangeB
							slave's merge(i, k)
							set i to i + 1
						end repeat
						exit repeat
					end if
				else
					-- The left value's less than or equal to the right. Assign it to this slot in the original list.
					set item k of my lst to lv
					slave's merge(i, k)
					-- Get the next left value. If none, simply exit this repeat and recursion level, as the remaining right values are from the remaining slots anyway.
					if (i < endLeft) then
						set i to i + 1
						set lv to item i of my rangeB
					else
						exit repeat
					end if
				end if
			end repeat
		end msrt
		
		-- Sort the "half-range" l thru r.
		on sortHalf(l, r)
			-- If there are more than two items in this half, recurse further to merge-sort them.
			-- If there are two, simply swap them (if necessary). If one, do nothing.
			if (r - l > 1) then
				msrt(l, r)
			else if (r > l) then
				set lv to item l of my lst
				set rv to item r of my lst
				if (comparer's isGreater(lv, rv)) then
					set item l of my lst to rv
					set item r of my lst to lv
					slave's swap(l, r)
				end if
			end if
		end sortHalf
		
		-- Default comparison and slave handlers, for a normal, mergeSort-style sort.
		on isGreater(a, b)
			(a > b)
		end isGreater
		
		on extract(a, b)
		end extract
		
		on merge(a, b)
		end merge
		
		on swap(a, b)
		end swap
	end script
	
	-- Process the input parmeters.
	set listLen to (count ListToSort)
	if (listLen > 1) then
		-- Negative and/or transposed range indices.
		if (l < 0) then set l to listLen + l + 1
		if (r < 0) then set r to listLen + r + 1
		if (l > r) then set {l, r} to {r, l}
		
		-- Supplied or default customisation scripts.
		if (customiser's class is record) then set {comparer:o's comparer, slave:o's slave} to (customiser & {comparer:o, slave:o})
		
		-- Do the sort.
		o's msrt(l, r)
	end if
	
	return -- nothing
end CustomMergeSort

property sort : CustomMergeSort


--Hash table implementation by DJ Bazzie Wazzie at Macscripter:
--http://macscripter.net/viewtopic.php?id=39424
script hashTable
	on newInstance()
		script hashTableInstance
			property parent : hashTable
			property size : missing value
			property hashList : missing value
			property keyList : missing value
			property valueList : missing value
		end script
	end newInstance
	
	on init()
		set my size to 0
		set my hashList to {}
		repeat HTableSize times
			set end of my hashList to {}
		end repeat
		set my keyList to {}
		set my valueList to {}
		return me
	end init
	
	on initWithKeysAndValues(_keys, _values)
		set my keyList to _keys
		set my valueList to _values
		set my size to count my keyList
		set my hashList to {}
		repeat HTableSize times
			set end of my hashList to {}
		end repeat
		repeat with x from 1 to my size
			set end of item hashFunction(item x of my keyList) of my hashList to x
		end repeat
		return me
	end initWithKeysAndValues
	
	on setValueforKey(_key, _value)
		set x to hashFunction(_key)
		repeat with node in item x of my hashList
			if id of item node of my keyList = id of _key then
				set item node of my valueList to _value
				return true
			end if
		end repeat
		set my size to (my size) + 1
		set end of my valueList to _value
		set end of my keyList to _key
		set end of item x of my hashList to my size
	end setValueforKey
	
	on valueForKey(_key)
		set x to hashFunction(_key)
		repeat with node in item x of my hashList
			if id of item node of my keyList = id of _key then
				return item node of my valueList
			end if
		end repeat
		return missing value
	end valueForKey
	
	on keyExists(_key)
		considering case
			return my keyList contains _key
		end considering
	end keyExists
	
	on keys()
		return my keyList
	end keys
	
	on setKeys(_keys)
		set _keys to every text of _keys
		if (count _keys) = (count my keyList) then
			set my keyList to _keys
			--need to re-hash
			set my hashList to {}
			repeat HTableSize times
				set end of my hashList to {}
			end repeat
			repeat with x from 1 to my size
				set end of item hashFunction(item x of my keyList) of my hashList to x
			end repeat
		end if
	end setKeys
	
	on valueExists(_value)
		return my valueList contains _value
	end valueExists
	
	on values()
		return my valueList
	end values
	
	on setValues(_values)
		if (count _values) = (count my valueList) then set my valueList to _values
	end setValues
	
	on count
		return my size
	end count
	
	on hashFunction(_key)
		set _hash to count _key
		--Credits to Shane Stanly here, supports single character keys now.
		repeat with char in (id of _key as list)
			set _hash to _hash + char
		end repeat
		return _hash mod HTableSize + 1
	end hashFunction
end script

As someone said, life is a long and lonely road, until you meat someone. :slight_smile:

Heart-warming and disturbing at the same time :cool: