How do I debug AppleScript? - debugging

What tips and tricks do you have for debugging AppleScript? Is there a debugger? If not, what is the best way to insert "prints" to display the value of variables? Is there a way to "pretty print" more complicated data structures?

The latest versions of xcode will let you create an AppleScript application but breakpoints in applescript don't work since Apple has discontinued support for AppleScript debugging in xcode.
Fallback: for simple "printf style" debugging you could use:
display dialog "my variable: " & myVar

Script Debugger
XCode
Getting the properties of an object (see below) to understand why it fails, when run from script editor. You can also use the class word, to see what class a property is. The Dictionary for an app is a good starting point.
One technique that often would have helped me, (and that I still sometimes do) is to tell something to return their properties, like this:
tell application "TextEdit"
get properties
end tell
Log statements and Console.app, for debugging of runtime events. (further below). You can ofcourse turn debugging on and off by setting a property
Below is a techniuqe I use for tracking runtime errors, in applets, mail rules, and what have you. When it fails, the error number and message is logged into TestDrive.log, and can be found in the left margin of Console.app…
tell application "TextEdit"
try
set a to text 0 of its name
on error e number n
my logit("OOPs: " & e & " " & n, "TestDrive")
end try
end tell
to logit(log_string, log_file)
do shell script ¬
"echo `date '+%Y-%m-%d %T: '`\"" & log_string & ¬
"\" >> $HOME/Library/Logs/" & log_file & ".log"
end logit

If you're building any amount of AppleScripts, ScriptDebugger is the best tool I can recommend. Having said that...
Xcode is a free option that can be used to develop AppleScripts and can step through code with the debugger. The ability is primarily included so you can build Cocoa applications with AppleScript Studio, but you could use it for any AppleScript development.
If you're looking for something simpler, you might check out Smile, which isn't really a debugger, but does offer features useful for debugging that aren't available in the standard Script Editor.

If a display dialog is too small you can use TextEdit to show big returns:
tell application "TextEdit"
activate
make new document
set text of document 1 to myResults
end tell
Source http://forums.macrumors.com/showthread.php?t=446171

To get the names of windows and other GUI elements and properties, I've found UI Browser invaluable. You can use it to inspect whatever you want to control with AppleScript to find the designations of the elements you want to control
Not free, but easily worth it for a serious developer.

Use the log command. Example:
log "Hello world!"
The output can then be seen in the "Messages" windows in the official editor.
Reference

Related

exploring avaliable commands in an applescript program

I have an application and I can do the following command on (I know this because I googled for it):
tell app "TextMate" to reload bundles
What I would really like is to be able to ask the "TextMate" program:
tell app "TextMate" to list all commands
and have it list out all the things I can ask it to do:
... 'reload bundles', 'exit', 'open files'...
is there a way to do that with applescript?
The way to find all the commands of an app is to open its dictionary in your script editor. Usually "Open Dictionary ... " in the File menu, or drop the application onto the script editor.
[EDIT]
For applications that have AppleScript support, you can actually script opening the app itself with the Script Editor, a la:
set pathToApp to (choose file of type "APPL")
tell application "Script Editor"
open pathToApp
end tell
BUT this will be problematic with a non-scriptable app. You'll get an error, but Script Editor will actually open some part of the app (and it will be slow about it), then give you an unusable document. There's no way to catch this error. If you use the Smile script editor, you can use this method ...
set p to (choose file of type "APPL")
try
OpenDictionary(alias (p as string))
on error e
end try
... to open the dictionary of an app, and if it doesn't work (if the app doesn't have a dictionary), it returns an error but doesn't do anything else (but again, you can't catch the error and not have it complain, without hacking Smile)
[EDIT 2]
A rabbit hole to go down is trying System Events or the Finder to check for boolean of has scripting terminology property of a process, but I don't recommend it because I haven't found it to be reliable.
[EDIT 3]
Ach! I knew there was another method, but forgot what it was. As #mklement0 points out (thank you), you can do this to check for an app's script-ability prior to opening the app in Script Editor:
set pathToApp to (choose file of type "APPL") as text
set isScriptable to false
try
class of application pathToApp
-- only AppleScriptable applications have a class property
set isScriptable to true
end try
isScriptable

How to set the ContentsChanged to true of the currently active window (black dot in close button)?

How is it possible to set the black dot in the close button of a window using applescript?
From http://docs.realsoftware.com/index.php/Window.ContentsChanged_property, I would guess something like:
tell app "CurrentApp" to set ContentsChanged to true of window 1
But it doens't work: execution error: CurrentApp got an error: Can’t make ContentsChanged of window 1 into type specifier. (-1700)
Also, how can I get what the current application is with applescript?
This question is related to Signifying unsaved changes by prepending * in window title - how to add a black dot in the window close button on Mac OS X?
To answer the second part of your question s first: you can get the current application with "the frontmost application", for example
tell application "Finder"
set frontApp to the path to the frontmost application
end tell
The short answer to the first part of your question is: it is currently impossible to do what you want. Long answer follows below.
From your comments, I understand you are using Python with TCL/TK to build the GUI.
Not every application is able to set the "contentsChanged" indicator (apparently, this is what it is called in RealBasic but not in Apple's API). Check the AppleScript dictionary of the application. Relying on what I read elsewhere, TextEdit used to be able to set the "modified" property of its documents but when I try this in Mac OS X 10.6.8 it no longer works.
If you're using a native Cocoa application, you might be able to set this indicator by adding and removing a space to the document with some GUI scripting, e.g. type a space and use the Undo menu item to remove it. Unfortunately, when I try this in TextEdit, the Undo command also removed the indicator.
I checked Apple's API documentation and there appears to be a method SetDocumentEdited in the NSWindow class. Unfortunately, Apple doesn't describe properly what it does, but it appears to set the dark spot in the red close button if the DocumentEdited property is set to true. You can read about it here. If you're using TCL/TK, it would have to be able to call the SetDocumentEdited method somehow.
You can't tell the operating system to change the DocumentEdited property of a window (which would be the equivalent of the "modified" property you found in an AppleScript dictionary). The (Python-TCL/TK) application you're talking to needs to have this implemented. If TCL/TK doesn't have a command for this, then it is probably impossible, no matter if you use AppleScript or Python or something else. I'd suggest you ask on a TCL/TK forum if someone can write and compile a kind of plug-in to implement this feature.

osacompile changing the AppleScript output so it won't run

I have a semi-long AppleScript that I run each morning to launch all of my apps etc. One of the things that it does is launch a few apps and then immediately minimize them. When I paste the .applescript source into Script Editor and run it, everything works fine:
-- snip:
tell application "Mail"
launch
minimize(window 1) of me
check for new mail
end tell
-- 'minimize' defined as:
on minimize(w)
set the miniaturized of w to true
end minimize
But when I compile the AppleScript source as follows:
osacompile -o ~/Library/Scripts/myscript.scpt myscript.applescript
... the compiler munges minimize to be:
on minimize(w)
set |miniaturized| of w to true
end minimize
And I get this error:
error "Mail got an error: Can’t make |miniaturized| of window id 30936 into type reference." number -1700 from |miniaturized| of window id 30936 to reference
Anyone have any clue what I'm doing wrong here? For purposes of version control, I need to run the scripts through osacompile.
UPDATE: To clarify, what seems to be happening is that Script Editor is compiling the method differently than osacompile on the command line. Is it known whether they compile different (e.g., using scope inferences or some such thing)?
There is nothing wrong with your code - I suspect this is a bug in osacompile and I suggest you file a bug report with Apple - as I've done.
You can verify that your code works correctly by using AppleScript Editor to save it as a *.scpt file directly and then running it with osascript.
[Updated] By contrast, passing the *.applescript source-code file directly to osascript does exhibit the problem.
There is no good reason I can think of for AppleScript Editor-based compilation to work differently from osacompile (and on-demand compilation in osascript), and the former's behavior is the expected and desired one in this case.
There are 2 workarounds:
Enclose the reference to miniaturized in a using terms from application "System Events" block:
This is a generic workaround that should work with windows from any AppleScriptable application.
on minimize(w)
using terms from application "System Events" # WORKAROUND
set miniaturized of w to true
end using terms from
end minimize
Inline the miniaturization command instead of calling a subroutine:
set miniaturized of window 1 to true
on minimize(w)
set the miniaturized of w to true
end minimize
This is not correct. You have "miniaturized" outside any application tell block of code, which means that it's an applescript command. It isn't an applescript command though. It's a command of Mail and other applications. It's amazing that it works properly in AppleScript Editor. It really shouldn't. When you have a command that doesn't make sense applescript is good enough to try to make sense of it and sometimes it can overcome your coding mistake. Obviously osacompile can't overcome your coding mistake.
So the way to fix the osacompile issue is to eliminate the coding mistake. You need to have the miniaturized command inside of an application tell block of code.
To see what I mean open the "standard additions" applescript dictionary and try to find the miniaturized command. you won't find it. Now open Mail's applescript dictionary and you will find it. Thus the command belongs to Mail, not applescript.

Create AppleScript for a program that isn't installed on the current computer

I'm trying to make two copies of an AppleScript, one that works for Entourage and one for out Outlook. I only have Entourage installed on the current computer.
According to the info on Microsoft's site, both applications have the same library of AppleScript commands, and I should be able to simply change the application name referenced within the script.
Changing:
Tell application "Microsoft Entourage"
to
Tell application "Microsoft Outlook"
Prevents me from saving the script because I don't have outlook installed on this computer. Is there any way around this? Do I need to use a text editor to edit the actual script file and change it there?
Thanks!
The following work-around may do the trick. On the computer where Entourage is installed, a using terms directive will let you compile the script, even if Outlook is not installed:
set theApp to a reference to application "Microsoft Outlook"
using terms from application "Microsoft Entourage"
tell theApp
get version
...
end tell
end using terms from
Upon compiling and saving the script the AppleScript Editor will bug you about the missing Outlook application, but it will nevertheless produce a compiled AppleScript file (.scpt).
Applescript is a pre-complied file format, meaning that every time you click "Save" it runs through a series of steps to ensure the script will work, but just short of actually running through the script's logic. Part of those steps is to look for the application to see if it exists on the Mac.
In short, if you want to save the script as an Applescript, you need the target application installed, otherwise you can save the script as a text file and move the file over to the target Mac to save as an Applescript over there.
It should be possible to make one script that works with both Entourage and Outlook, without bugging you if one isn't found either when you compile or when you run. I don't have either Entourage or Outlook but it should work like this:
using terms from application "Microsoft Entourage"
script theScript
tell application "Finder" to try
set theApp to application file id "Entourage's Bundle ID" as text
on error
set theApp to application file id "Outlook's Bundle ID" as text
end try
tell application theApp
-- do stuff
end tell
end script
end using terms from
store script theScript in "MyScript.scpt"
"using terms from" is only relevant when compiling the script - it isn't needed when running, though for some reason you'll still get bugged if that app isn't found. So by wrapping it around a script object and then writing out that script to file, the resultant script will still run but won't contain "using terms from" and so won't bug the user.
For getting a reference to the right app, Finder can look for it by ID and simply error if it isn't found rather than bugging the user. You'll need to insert the proper ID's there, I don't know what they are.

Logging in AppleScriptObjC in Xcode

I'm writing code in AppleScript to glue an Obj-C Cocoa app to some other stuff. I'm very unfamiliar with AppleScript on also learning Cocoa, so of course I have all kinds of bugs in my code to work out, and I need at least some logging.
However, output from the AppleScript 'log' command doesn't seem to end up in XCode's debugger console and calling NSLog doesn't seem to work. Is there any way I can send output to the Debugger Console from within an AppleScriptObjC class method?
(suggestion: new applescriptobjc tag on this Q -- I can't create new tags yet)
I don't use applescriptobjc, so I'm not sure. However, I used to use Applescript Studio so maybe my experience there applies. I noticed that you can't have a log (or NSLog) statement inside of an application tell block of code. Basically if you do that then you are telling the application to log something and the application doesn't know the log command... so it wouldn't work. As such you have to get your log statements out of application tell blocks or use use tell me to log "something" in the tell block... which essentially tells applescript to do the logging.
Not ideal because this logs to the console and uses the shell, but this should at least get you something that works:
log_entry("Hello, World!")
on log_entry(theLine)
do shell script "echo " & theLine & " >> ~/Library/Logs/AppleScript-events.log"
end log_entry

Resources