"Include" files, global scoping, and more

Hello,

I have an AppleScript that needs to have a customized version to be run on hundreds of machines, each machine having a different version of the script. My thought was to separate the customized details into one file, and then have a “codebase script” (CodeBaseTest.scpt) file that would be included on them. That way, I could maintain one codebase file more easily.

Because I don’t know the version of macOS running (and it may be as far back as 10.13), I need to keep things super simple. Furthermore, because they won’t have script libraries installed, I can’t use that mechanism for a codebase lib. That also rules out the third party tools, I believe, that make it easier to produce scripts with includes (whether a library, or something like Script Debugger).

So, I’ve gone ahead and split it to 2 scripts:

first.last.scpt << customized for each machine
CodeBaseTest.scpt << is copied to the same folder as first.last.scpt so it’s in the same folder

To make this work, and to make it easy to test new versions of code in the code base, I wanted to make the “on run” handler the same in both files – but this isn’t a must, just made it easier to do things.

Also to make it work, I had to remove all the properties and move to globals with declarations and then a set to make the defaults work.

The problem is that the scripts are not seeing variables that are clearly defined as globals.

There are two sets of these two files below – a short set, and a set that has a ton of debug code in the two files here (which will make the errors very obvious if you run them).

Short set first

first.last-test.scpt here:


on run
	global myPath, myName, scriptFileExtension, myLibFilename, myLib
	set myPath to (path to me) as text
	set myName to ((name of me) as text)
	set scriptFileExtension to ".scpt"
	set myLibFilename to "CodeBaseTest" & scriptFileExtension
	
	if (myName & scriptFileExtension) is not myLibFilename then
		set myLib to load script (text 1 through ((length of myPath) - (length of (myName & scriptFileExtension))) of myPath & myLibFilename) as alias
	else if (myName & scriptFileExtension) is myLibFilename then
		set myLib to me
	else
		set fullErrorMsg to "Critical error assigning myLib or loading script. Script terminating. (main on run handler)" & return
		log fullErrorMsg
		display dialog fullErrorMsg
	end if
	
	(*	—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-
		Assumptions that need to be defined early on… and are done so in initializeGlobals()
		So that they can be used from either the user script (e.g., first.last.scpt) or test data
		within code base script (i.e., OutlookCodeBase.scpt). First a check which file we're in.
		—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-
		*)
	
	initializeGlobals() of myLib
	
	-- Account to copy to. If empty, defaults to first account in AcctsToAddList.
	global theDestAcctParams, theDestAcctParamNames
	set theDestAcctParams to {}
	set theDestAcctParamNames to {}
	
	(*	—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-
		Accounts to Add: Custom User Account Parameters -- defined for each user
		—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-
		*)
	global AcctsToAddList
	set AcctsToAddList to {¬
		{"Field1", "Field2", "Field3"}, ¬
		{"A", "B", "C"}, ¬
		{"D", "E", "F"}, ¬
		{EndOfListMarker}}
	
	-- Main Script, and supporting routines, are executed by a routine in the OutlookCodeBase.scpt file
	if (myName & scriptFileExtension) is not myLibFilename then
		mainScript() of myLib
	else
		mainScript()
	end if
	
end run

and CodeBaseTest.scpt (with debug code) here:


on initializeGlobals()
	-- Additional logging takes place with the debugFlag set to true
	global debugFlag
	set debugFlag to true as boolean
	
	global BlankUserPassword, EndOfListMarker
	set BlankUserPassword to "" -- should remain blank and not seen
	set EndOfListMarker to "ENDOFLIST"
	
end initializeGlobals

on mainScript()
	
	(*	—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-
		Log the time, so we know which run this is...
		—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-
		*)
	--if debugFlag then log "This run started at: " & (time string of (current date))
	
	-- if theDestAcctParams is empty, copy first account from AcctsToAddList
	if number of items in theDestAcctParams is 0 then
		if number of items in AcctsToAddList is greater than 1 then
			if debugFlag is true then log "theDestAcctParams is empty. copying 2nd item from AcctsToAddList"
			set theDestAcctParams to item 2 of AcctsToAddList
		else
			log "!!! ERROR: not enough items in AcctsToAddList to copy"
		end if
		if debugFlag is true then log "theDestAcctParams is not empty."
	end if
	
end mainScript

on run
	global myPath, myName, scriptFileExtension, myLibFilename, myLib
	set myPath to (path to me) as text
	set myName to ((name of me) as text)
	set scriptFileExtension to ".scpt"
	set myLibFilename to "CodeBaseTest" & scriptFileExtension
	
	if (myName & scriptFileExtension) is not myLibFilename then
		set myLib to load script (text 1 through ((length of myPath) - (length of (myName & scriptFileExtension))) of myPath & myLibFilename) as alias
	else if (myName & scriptFileExtension) is myLibFilename then
		set myLib to me
	else
		set fullErrorMsg to "Critical error assigning myLib or loading script. Script terminating. (main on run handler)" & return
		log fullErrorMsg
		display dialog fullErrorMsg
	end if
	
	(*	—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-
		Assumptions that need to be defined early on… and are done so in initializeGlobals()
		So that they can be used from either the user script (e.g., first.last.scpt) or test data
		within code base script (i.e., OutlookCodeBase.scpt). First a check which file we're in.
		—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-
		*)
	
	initializeGlobals() of myLib
	
	-- Account to copy to. If empty, defaults to first account in AcctsToAddList.
	global theDestAcctParams, theDestAcctParamNames
	set theDestAcctParams to {}
	set theDestAcctParamNames to {}
	
	(*	—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-
		Accounts to Add: Custom User Account Parameters -- defined for each user
		—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-
		*)
	global AcctsToAddList
	set AcctsToAddList to {¬
		{"Field1", "Field2", "Field3"}, ¬
		{"A", "B", "C"}, ¬
		{"D", "E", "F"}, ¬
		{EndOfListMarker}}
	
	-- Main Script, and supporting routines, are executed by a routine in the OutlookCodeBase.scpt file
	if (myName & scriptFileExtension) is not myLibFilename then
		mainScript() of myLib
	else
		mainScript()
	end if
	
end run

Anyone have any idea why this isn’t working? I’m sure (and hopeful) that I’m doing something stupid … after all, I am coding during a migraine … but I have to get this done today!

Thanks for reading (and even more if you can help)!
Neil

============================================================================
The below is just the same scripts, but the set with all the extra debug code:
============================================================================

first.last-test.scpt here:

on run
	global actionsHistory
	set actionsHistory to ""
	
	global myPath, myName, scriptFileExtension, myLibFilename, myLib
	set myPath to (path to me) as text
	set myName to ((name of me) as text)
	set scriptFileExtension to ".scpt"
	set myLibFilename to "CodeBaseTest" & scriptFileExtension
	
	set actionsHistory to actionsHistory & return & return & ¬
		"• " & ("hello world from " & myName)
	display dialog ("hello world from " & myName)
	
	if (myName & scriptFileExtension) is not myLibFilename then
		set myLib to load script (text 1 through ((length of myPath) - (length of (myName & scriptFileExtension))) of myPath & myLibFilename) as alias
	else if (myName & scriptFileExtension) is myLibFilename then
		set myLib to me
	else
		set fullErrorMsg to "Critical error assigning myLib or loading script. Script terminating. (main on run handler)" & return
		log fullErrorMsg
		display dialog fullErrorMsg
		set actionsHistory to actionsHistory & return & return & ¬
			"• " & fullErrorMsg
	end if
	
	(*	—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-
		Assumptions that need to be defined early on… and are done so in initializeGlobals()
		So that they can be used from either the user script (e.g., first.last.scpt) or test data
		within code base script (i.e., OutlookCodeBase.scpt). First a check which file we're in.
		—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-
		*)
	try
		initializeGlobals() of myLib
		(*
		if (myName & scriptFileExtension) is not myLibFilename then
			initializeGlobals() of myLib
		else
			initializeGlobals()
		end if
	*)
	on error errorStr number errorNum partial result resultList
		set fullErrorMsg to ("Error is: " & errorStr & return & "Error number: " & errorNum & return & "Result List: " & resultList as text) ¬
			& return & "Critical error initializing globals. Script terminating. (attempt to call initializeGlobals() in " & myName & ")"
		log fullErrorMsg
		display dialog fullErrorMsg
		set actionsHistory to actionsHistory & return & return & ¬
			"• " & fullErrorMsg
	end try
	
	-- Account to copy to. If empty, defaults to first account in AcctsToAddList.
	global theDestAcctParams, theDestAcctParamNames
	set theDestAcctParams to {}
	set theDestAcctParamNames to {}
	
	(*	—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-
		Accounts to Add: Custom User Account Parameters -- defined for each user
		—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-
		*)
	global AcctsToAddList
	set AcctsToAddList to {¬
		{"Field1", "Field2", "Field3"}, ¬
		{"A", "B", "C"}, ¬
		{"D", "E", "F"}, ¬
		{EndOfListMarker}}
	
	-- Main Script, and supporting routines, are executed by a routine in the CodeBaseTest.scpt file
	if (myName & scriptFileExtension) is not myLibFilename then
		mainScript() of myLib
	else
		mainScript()
	end if
	
	display dialog "Action history is: " & return & return & actionsHistory
	
end run

and CodeBaseTest.scpt (with debug code) here:

on initializeGlobals()
	display dialog ("hello world from initializeGlobals() in CodeBaseTest.scpt")
	try
		-- Additional logging takes place with the debugFlag set to true
		global debugFlag
		set debugFlag to true as boolean
		
		global BlankUserPassword, EndOfListMarker
		set BlankUserPassword to "" -- should remain blank and not seen
		set EndOfListMarker to "ENDOFLIST"
		
	on error errorStr number errorNum partial result resultList from badObject to expectedType
		set fullErrorMsg to (("Error is: " & errorStr & return * " Error number: " & errorNum & return ¬
			& "in script: " & myName & return ¬
			& "Result List: " & resultList as text) & return ¬
			& "badObject: " & badObject as text) & return ¬
			& "Coercion failure: " & expectedType
		log fullErrorMsg
		display dialog fullErrorMsg
		set actionsHistory to actionsHistory & return & return & ¬
			"• " & fullErrorMsg
	end try
	
	display dialog ("In routine " & "initializeGlobals" & " (end)" & " in " & myName & " with debugFlag = " & debugFlag)
end initializeGlobals

on mainScript()
	display dialog ("hello world from mainScript() in CodeBaseTest.scpt")
	
	(*	—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-
		Log the time, so we know which run this is...
		—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-
		*)
	try
		display dialog ("In routine " & "mainScript" & " (start)" & " with debugFlag = " & debugFlag)
		if debugFlag then log "This run started at: " & (time string of (current date))
	on error errorStr number errorNum partial result resultList from badObject to expectedType
		set fullErrorMsg to ("Error is: " & errorStr & return & " Error number: " & errorNum & return ¬
			& "Result List: " & (resultList as text) & return ¬
			& "badObject: " & (badObject as text) & return ¬
			& "Coercion failure: " & expectedType)
		log fullErrorMsg
		display dialog fullErrorMsg
		set actionsHistory to actionsHistory & return & return & ¬
			"• " & fullErrorMsg
	end try
	
	-- if theDestAcctParams is empty, copy first account from AcctsToAddList
	try
		if number of items in theDestAcctParams is 0 then
			if number of items in AcctsToAddList is greater than 1 then
				if debugFlag is true then log "theDestAcctParams is empty. copying 2nd item from AcctsToAddList"
				set theDestAcctParams to item 2 of AcctsToAddList
			else
				log "!!! ERROR: not enough items in AcctsToAddList to copy"
			end if
			if debugFlag is true then log "theDestAcctParams is not empty."
		end if
	on error errorStr number errorNum partial result resultList from badObject to expectedType
		set fullErrorMsg to ("Error is: " & errorStr & return & " Error number: " & errorNum & return ¬
			& "Result List: " & (resultList as text) & return ¬
			& "badObject: " & (badObject as text) & return ¬
			& "Coercion failure: " & expectedType)
		log fullErrorMsg
		display dialog fullErrorMsg
		set actionsHistory to actionsHistory & return & return & ¬
			"• " & fullErrorMsg
	end try
	
end mainScript

on run
	global actionsHistory
	set actionsHistory to ""
	
	global myPath, myName, scriptFileExtension, myLibFilename, myLib
	set myPath to (path to me) as text
	set myName to ((name of me) as text)
	set scriptFileExtension to ".scpt"
	set myLibFilename to "CodeBaseTest" & scriptFileExtension
	
	set actionsHistory to actionsHistory & return & return & ¬
		"• " & ("hello world from " & myName)
	display dialog ("hello world from " & myName)
	
	if (myName & scriptFileExtension) is not myLibFilename then
		set myLib to load script (text 1 through ((length of myPath) - (length of (myName & scriptFileExtension))) of myPath & myLibFilename) as alias
	else if (myName & scriptFileExtension) is myLibFilename then
		set myLib to me
	else
		set fullErrorMsg to "Critical error assigning myLib or loading script. Script terminating. (main on run handler)" & return
		log fullErrorMsg
		display dialog fullErrorMsg
		set actionsHistory to actionsHistory & return & return & ¬
			"• " & fullErrorMsg
	end if
	
	(*	—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-
		Assumptions that need to be defined early on… and are done so in initializeGlobals()
		So that they can be used from either the user script (e.g., first.last.scpt) or test data
		within code base script (i.e., OutlookCodeBase.scpt). First a check which file we're in.
		—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-
		*)
	try
		initializeGlobals() of myLib
		(*
		if (myName & scriptFileExtension) is not myLibFilename then
			initializeGlobals() of myLib
		else
			initializeGlobals()
		end if
	*)
	on error errorStr number errorNum partial result resultList
		set fullErrorMsg to ("Error is: " & errorStr & return & "Error number: " & errorNum & return & "Result List: " & resultList as text) ¬
			& return & "Critical error initializing globals. Script terminating. (attempt to call initializeGlobals() in " & myName & ")"
		log fullErrorMsg
		display dialog fullErrorMsg
		set actionsHistory to actionsHistory & return & return & ¬
			"• " & fullErrorMsg
	end try
	
	-- Account to copy to. If empty, defaults to first account in AcctsToAddList.
	global theDestAcctParams, theDestAcctParamNames
	set theDestAcctParams to {}
	set theDestAcctParamNames to {}
	
	(*	—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-
		Accounts to Add: Custom User Account Parameters -- defined for each user
		—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-
		*)
	global AcctsToAddList
	set AcctsToAddList to {¬
		{"Field1", "Field2", "Field3"}, ¬
		{"A", "B", "C"}, ¬
		{"D", "E", "F"}, ¬
		{EndOfListMarker}}
	
	-- Main Script, and supporting routines, are executed by a routine in the CodeBaseTest.scpt file
	if (myName & scriptFileExtension) is not myLibFilename then
		mainScript() of myLib
	else
		mainScript()
	end if
	
	display dialog "Action history is: " & return & return & actionsHistory
	
end run

Might help to know what you’re trying to do here on the different macs

All your global variable declarations are inside handlers, so their globality is 0. You have to take all these declarations outside of handlers, even outside of the run() handler.

You can also initialize them with some values inside the handlers.


global actionsHistory
global myPath, myName, scriptFileExtension, myLibFilename, myLib

on run {}
	set actionsHistory to ""
	set myPath to (path to me) as text
	set myName to ((name of me) as text)
	set scriptFileExtension to ".scpt"
	set myLibFilename to "CodeBaseTest" & scriptFileExtension
	-- the rest of on handler stuff
	my displayExtension() -- ADDED BY ME TO TEST
	my onceAgain() -- ADDED BY ME TO TEST OTHER WAY
end run

on displayExtension() -- ADDED BY ME TO TEST
	display dialog scriptFileExtension
end displayExtension

on onceAgain() -- ADDED BY ME TO TEST OTHER WAY
	set scriptFileExtension to "Script File Extension:   " & scriptFileExtension
	my displayExtension()
end onceAgain

This seems to be the case here, looking quickly at neilticktin’s scripts.

But just to mention as a generality, globals can be declared inside handlers. In such cases, provided they’re not also declared at the top of the script, they’re global only amongst those handlers and the run handler. It can be a confusing topic. :confused: The general wisdom is to avoid globals if possible.

on run
	handler1()
	handler2()
	say result
	say aGlobal
	handler3()
end run

on handler1()
	global aGlobal
	set aGlobal to "Hello"
end handler1

on handler2()
	global aGlobal
	return aGlobal
end handler2

on handler3()
	return aGlobal
end handler3