Hi,
the script below is my attempt at solving the long-standing problem of importing external AppleScript scripts easily. It is a lightweight solution, in the sense that it is made of just a handler and a property, but I have used it satisfactorily for some time, so I thought I might share it. For a more complete solution, you may want to look at Loader.
The script mainly aims at easing development (for deployment you may want somehow to package scripts together). The main advantage, apart from helping achieve code modularity, is that you can reorganize your scripts in the Finder without (probably) any need to change the code that imports them.
For example, say your project folder is organized like this:
and you also have ~/Library/Scripts/Reusable/Stuff/Lib3.scpt. Then, Main.scpt might look like this:
import("Lib1")
import("Lib2")
import("Lib3")
-- Use the imported scripts:
Lib1's f()
Lib2's g()
tell Lib3 to run somescript
-- Here goes import's definition
The script is thoroughly commented: if you have installed Xcode 4.1 or later, you may get pretty-printed documentation by typing the following in Terminal.app:
Import script:
(*!
@header
Import
@abstract
Easily import other scripts, without worrying too much about their location.
@discussion
Copy and paste the code of this file into your main script (the best location is probably at the very bottom of the script, so that it will stay out of your sight). Then use
<pre>
import("Script Name")
</pre>
at the beginning of your script to import an external script. By default, @link import @/link searches the main script's directory and its subfolders, and the user's Scripts folder and its subfolders (in that order). It efficiently does so using Spotlight; hence, Spotlight must be on and your disk must have been indexed for @link import @/link to work. If the external script is located elsewhere, the @link libpath @/link property can be used to specify a list of additional search paths. For example, if <tt>MyLib.scpt</tt> is in (a subfolder of) the Downloads folder then it can be imported as follows:
<pre>
set libpath to {path to downloads folder}
import("MyLib")
</pre>
The imported scripts can be in source form (<tt>.applescript</tt>) or compiled form (<tt>.scpt</tt>). When both variants of a script exist, the compiled script is preferred over the textual script. When multiple versions of the same script exist in different locations, the first one to be found is the one that is imported: user-defined locations are searched first, followed by the script's folder, followed by <tt>~/Library/Scripts/</tt>.
<strong>Important</strong>: each external script loaded by @link import @/link as shown above should set a variable to itself and return itself. This can be achieved by defining the external script's <tt>run</tt> handler as follows:
<pre>
on run
global MyLib -- Optional
set MyLib to me
return me
end
</pre>
(The <tt>global</tt> declaration is optional: variables defined in the top-level run handler are implicitly global.)
This way, thanks to the somewhat convoluted scoping rules of AppleScript, the external script becomes automagically available at the top-level of the main script through the variable <tt>MyLib</tt> (it is not mandatory, but strongly recommended, that the variable's name and the script's file name be the same). Note, however, that, by the same mysterious rules, <tt>MyLib</tt> will not be accessible in nested scopes, that is, <tt>MyLib</tt> becomes <em>implicitly local</em> at the top-level of the main script. To make it global, you should add <tt>global MyLib</tt> at the top-level of your main script, or add a <tt>global MyLib</tt> declaration in each nested scope that must access <tt>MyLib</tt>.
For scripts that you cannot modify, or if you want a script's name to be globally visible without an explicit <tt>global</tt> declaration, the script can be imported as follows:
<pre>
set ScriptName to import("ScriptName")
</pre>
Note, however, that, unless the external script returns itself, this will only work with compiled scripts.
@version 1.1.1
*)
(*!
@abstract
<em>[list]</em> A list of paths where scripts are located.
@discussion
By default, @link import @/link searches for scripts inside the directory where the loader script is located, then inside <tt>~/Library/Scripts/</tt>. This property can be used to specify a list of additional search paths, which take precedence over the default ones.
*)
property libpath : {}
(*!
@abstract
Import another script.
@discussion
This handler loads and runs the code from the given script. A pretty obvious constraint is that the external script should not raise errors upon running; this is easily satisfied by any sensible library code.
@param scriptName <em>[text]</em> The name of the script to be loaded, <em>without suffix</em>.
@return <em>[script]</em> The imported script (unless the imported script is a text file (<tt>.applescript</tt>) <em>and</em> it does not return itself, in which case import's return value will be whatever the imported script returns).
*)
on import(scriptName)
local pathList, lib, searchPath, scriptPaths, pathname
set pathList to my libpath & {folder of file (path to me) of application "Finder", path to scripts folder from user domain}
repeat with searchPath in pathList
set scriptPaths to the paragraphs of (do shell script "mdfind -literal -onlyin " & quoted form of POSIX path of (searchPath as alias) & " 'kMDItemFSName == " & quote & scriptName & ".*" & quote & "cd'")
repeat with pathname in scriptPaths
try
if pathname ends with ".scpt" then
set lib to load script pathname
else if pathname ends with ".applescript" then
set lib to run script pathname
else
error -- Not an AppleScript script
end if
log " Script loaded from " & pathname & " "
exit repeat
end try
end repeat
try
run lib
log " " & scriptName & " imported. "
exit repeat
end try
end repeat
try
lib
on error
repeat with searchPath in pathList
set the contents of searchPath to searchPath as text
end repeat
set AppleScript's text item delimiters to return
error "No script named " & quote & scriptName & quote & " was found." & return & return & "Search path: " & return & (pathList as text)
end try
end import
Finally, to avoid copying and pasting every time, you may define an AppleScript Editor’s shortcut. Just save the following script as “Import Script.scpt” and copy it into /Library/Scripts/Script Editor Scripts. It will become available in the contextual menu (which appears when you right-click on an editor’s window).
(* Import Script
Code adapted from Apple sample code, but changed by druido for Macscripter's users.
Still provided "AS IS".
*)
set the target_string to "X-X-X"
set the script_text to "property libpath : {}" & return ¬
& "on import(scriptName) -- v1.1.1" & return ¬
& "local pathList, lib, searchPath, scriptPaths, pathname" & return ¬
& "set pathList to my libpath & {folder of file (path to me) of application \"Finder\", path to scripts folder from user domain}" & return ¬
& "repeat with searchPath in pathList" & return ¬
& "set scriptPaths to the paragraphs of (do shell script \"mdfind -literal -onlyin \" & quoted form of POSIX path of (searchPath as alias) & \" 'kMDItemFSName == \" & quote & scriptName & \".*\" & quote & \"cd'\")" & return ¬
& "repeat with pathname in scriptPaths" & return ¬
& "try" & return ¬
& "if pathname ends with \".scpt\" then" & return ¬
& "set lib to load script pathname" & return ¬
& "else if pathname ends with \".applescript\" then" & return ¬
& "set lib to run script pathname" & return ¬
& "else" & return ¬
& "error" & return ¬
& "end if" & return ¬
& "log \" Script loaded from \" & pathname & \" \"" & return ¬
& "exit repeat" & return ¬
& "end try" & return ¬
& "end repeat" & return ¬
& "try" & return ¬
& "run lib" & return ¬
& "log \" \" & scriptName & \" imported.\"" & return ¬
& "exit repeat" & return ¬
& "end try" & return ¬
& "end repeat" & return ¬
& "try" & return ¬
& "lib" & return ¬
& "on error" & return ¬
& "repeat with searchPath in pathList" & return ¬
& "set the contents of searchPath to searchPath as text" & return ¬
& "end repeat" & return ¬
& "set AppleScript's text item delimiters to return" & return ¬
& "error \"No script named \" & quote & scriptName & quote & \" was found.\" & return & return & \"Search path: \" & return & (pathList as text)" & return ¬
& "end try" & return ¬
& "end import" & return ¬
tell current application
activate
tell the front document
set the selected_text to contents of selection
if the selected_text is not "" then
if my display_message() is false then return "user cancelled"
end if
set the contents of selection to the script_text
try
check syntax
end try
my replace_and_select(target_string, "")
try
check syntax
end try
end tell
end tell
on display_message()
display dialog "This script will delete the selected text." & return & return & "Do you want to continue?" buttons {"Help", "Continue", "Stop"} default button 3 with icon 2
set the user_choice to the button returned of the result
if the user_choice is "Help" then
my script_help("ScriptEditor001")
return false
else if the user_choice is "Stop" then
return false
else
return true
end if
end display_message
on script_help(this_anchor)
ignoring application responses
tell application "HelpViewer"
activate
try
lookup anchor this_anchor in book "Script Editor Help"
end try
end tell
end ignoring
end script_help
on replace_and_select(target_string, replacement_string)
tell current application
tell the front document
set this_text to the contents
set this_offset to the offset of the target_string in this_text
if this_offset is not 0 then
set selection to characters this_offset thru (this_offset + (length of the target_string) - 1)
set the contents of the selection to the replacement_string
end if
end tell
end tell
end replace_and_select