Scripting and Macros
Mesquite's scripting system allows modules and other objects to be scripted using text commands. Mesquite uses scripting by text commands as follows:- When a menu item, button, or tool is used, Mesquite sends a text command to the appropriate object, informing it of the use. This means that most user interface commands can also be executed in scripts and macros.
- When Mesquite re-reads a NEXUS file, it attempts to return windows and analyses to approximately the same condition as when the file had been saved. It does this by reading scripting instructions in a MESQUITE block that Mesquite had written into the file when saved. Thus, Mesquite writes scripts called "Snapshots" to later instruct itself.
- Macros are script files which, if placed in the recognized directories, appear in submenus as selectable menu items. When selected, the macro is executed. Mesquite recognizes directories called "macros" within the Mesquite_Folder directory and the "Mesquite_Prefs" subdirectory of the Mesquite_Support_Files directory.
- Not all scripts in MESQUITE blocks need to be written by Mesquite. Users can write MESQUITE block scripts by hand.
- Users can send a script to a window using Window>Scripting>Send Script. This page gives some simple examples for the tree window.
The text commands are sent to modules or other objects. The particular text commands to which a module or other object responds will depend on the object. When Mesquite shows a web page for the first time, it attempts to compile automatically documentation for scripting commands that is available. This includes commands predefined by the scripting language, and commands belonging to particular modules. This compiled documentation is available from the web page linked from the "Scripting Commands" menu item in the Help menu when Mesquite is running.
Mesquite's scripting language is not as human-friendly as it might be at present, especially in its handling of variables and aspects such as the lack of "else" statements. Since the vast majority of Mesquite scripts won't be written by humans but by Mesquite itself, that is not such a liability as it might seem. We hope to reform the scripting language in the future.
Macros
Scripts performing some special function can be written and distributed by programmers to end users as "Macros". For some calculations or display functions, the number of different analyses a user might like to do are too many to be easily supported by a graphical user interface. For instance, the user might like to print the ancestral state reconstruction of a character using a series of different stepmatrices. One can invent many such scenarios in which something repetitious is needed, and a module would be hard-pressed to maintain each as an option. Thus, small scripts using Mesquite's scripting language can be written and placed in the "macros" folder. These macros will appear as options in an appropriate place, depending upon where they are applicable. When the user selects the menu item, the script is executed.Macros can also be automatically composed by Mesquite. As noted above, Mesquite composes a script to place in NEXUS files that it saves. This script applies only to the particular saved file. In some contexts, you can ask Mesquite to save a script as a macro file. This is currently available in only a few contexts. Save Window as Macro in the Analysis menu and Window menu save macros that attempt to reproduce the condition (including analyses) of the foremost window. This is useful to reproduce a complex chart with a different data file, for example. Save Macro For Tree Drawing in the Display menu of a Tree Window creates a macro by which you can later reproduce the current appearance of the tree drawing (background color, font, tree form, orientation, and so on). Save Tree Analysis As Macro in the Tree menu of a Tree window creates a macro by which to reproduce a particular analysis with the tree. Macros so created are stored in Mesquite_Support_Files/Mesquite_Prefs/macros, and could be (for instance) shared by users.
Learning about scripting
Users can learn about the scripting language from this web page, but also by inspection of existing scripts. Some sources of scripts to examine are:- The MESQUITE block within NEXUS files saved by Mesquite. If you want to know how to script a tree window to trace a character, for instance, open a tree window, turn on character tracing, and save the file. You can look at the file with a text editing program to see how such a script would be written
- If there are any macros installed, you can examine the files that specify them. These files are simple text files with scripts. Macros would be found in folders called "macros" within the Mesquite_Folder directory, and in the macros folder of the "Mesquite_Prefs" directory of the Mesquite_Support_Files directory.
- The menu item Window>Scripting>Show Snapshot shows a snapshot for the window — the script that would be needed to set the window to its current state.
Commands within scripts
In general, a command found within a script takes the following form:commandName argument1 argument2 ...;The commandName is a single word (token); the arguments can be multiple tokens, so long as the module knows how to interpret them. Typically each argument is a single token (though this may be string of multiple tokens converted to a single token by quotation).
Scripts
A script consists of a series of commands. At each stage in the script, there is an implicit recipient of the command given (for instance, a module or a window). For instance, here is a script within the MESQUITE block of a NEXUS file. To the right of the commands are comments to explain them.Begin MESQUITE; getNumberOfDataSets; [file coordinator queried for number of data sets] Integer.dataSets *it; [storing number of data sets in integer variable 'dataSets'] getEmployee 'Data Window Coordinator'; [querying for reference to Data Window Coordinator module] tell It; [commands to follow will be sent to the Data Window Coordinator] Integer.dataNum 0; [Define integer variable 'dataNum' and assign it 0] for *Integer.dataSets; [for loop; cycle as many times as there are data sets] showDataWindow *Integer.dataNum; [commands to make a data window for dataset] tell It; [commands to follow will be sent to module that makes data window] showWindow; [tells the module to make the data window visible] endTell; [finished sending commands to the module that makes the data window] increment.dataNum; [add 1 to the variable 'dataNum'] endFor; [end of the for loop] endTell; [finished sending commands to the Data Window Coordinator] getNumberOfTaxas; [file coordinator queried for number of sets of taxa] Integer.taxaSets *it; [storing number of sets of taxa in integer variable 'taxaSets'] getEmployee 'Tree Window Coordinator'; [querying for reference to Tree Window Coordinator module] tell It; [commands to follow will be sent to the Tree Window Coordinator] Integer.taxaNum 0; [Define integer variable 'taxaNum' and assign it 0] for *Integer.taxaSets; [for loop; cycle as many times as there are sets of taxa] makeTreeWindow *Integer.taxaNum; [commands to make a tree window for set of taxa] tell It; [commands to follow will be sent to module that makes tree window] getTreeWindow; [queries the module to return a reference to the tree window itself] tell It; [commands to follow will be sent to the tree window] newAssistant 'Trace Character History'; [the tree window is asked to hire a module] endTell; [finished sending commands to the tree window] showWindow; [tells the module to make the tree window visible] endTell; [finished sending commands to the module that makes the tree window] increment.taxaNum; [add 1 to the variable 'taxaNum'] endFor; [end of the for loop] endTell; [finished sending commands to the Tree Window Coordinator] END;
This MESQUITE block causes Mesquite to show a data window for each of the data sets, and a tree window for each of the Taxa blocks; the tree windows are shown with a character traced.
This script illustrates some of the features of Mesquite's scripting language:
- There is a current object to which commands are sent. Which object is being commanded can be changed by a "tell" command. Initially, the implicit recipient of the commands is the FileCoordinator of the file in question.
- Any given command may return an object that is stored within the variable referred to by "It". Thus, in the above script, the makeTreeWindow returns the module hired to supervise the tree window. The immediately following command, "tell It" thus shifts the recipient of commands to this tree window module.
- The scripting language has variables. They are defined or their values set by commands beginning, for instance, Integer.myIntegerName or Object.myObjectName. Their values can be utilized by prefixing their names by an asterisk, for instance *Integer.myIntegerName.
- The scripting language has control flow, including if, for loops, and while loops.
Variables: Integers, Strings and Objects
Three sorts of variables are supported. Reference to each requires the type of variable with name appended, as in "Integer.numberOfCharacters" or "Object.treeDrawCoordinator". The generic variable "it" refers to the object last returned by a command.When the variable is passed as an argument for a command, it should be preceeded by anasterisk. This allows the system to know that a variable, and not a constant string, is being passeed.
Numerical variables
Two sorts of numerical variables are supported: Integer and Number. The former contain whole numbers. The latter can contain whole or decimal numbers. Reference to each requires the type of variable with name appended, as in "Integer.numberOfCharacters" or "Object.treeDrawCoordinator". The generic variable "it" refers to the object last returned by a command.When the variable is passed as an argument for a command, it should be preceeded by an asterisk. This allows the system to know that a variable, and not a constant string, is being passeed. The commands concerning variables are:
- Integer.[name] [number];— this declares an integer variable of name "name" and assigns it the value given by the number. If the variable already exists, its value is replaced by the number. The number may be represented by a constant, as in "Integer.counter 5", by an integer variable, as in "Integer.counter *Integer.previousCount", or by a String variable that contains a string that can be parsed into an integer, as in "Integer.counter *String.countString". If the number is indicated as "random", a random integer will be placed in the variable.
- increment.[name of integer]; — this adds one to the integer's value
- decrement.[name of integer]; —this subtracts one from the integer's value.
- Number.[name] [number];— this declares a numerical variable of name "name" and assigns it the value given by the number. If the variable already exists, its value is replaced by the number. The number may be represented by a constant, as in "Number.rate 0.5", by an integer variable, as in "Number.rate *Number.previousRate", or by a String variable that contains a string that can be parsed into a number, as in "Number.rate *String.rateString". If the number is indicated as "random", a random number between 0 and 1 will be placed in the variable. If the number to be placed into the Number variable is preceeded by a '+', the number doesn't replace the existing value of the Number variable, but is added to it (similarly for '-').
String variables
One sort of variable contains a string of text. The command to define and assign values to a string variable is:- String.[name] [string]; — this declares a String variable of name "name" and assigns it the value given by the string. If the variable already exists, its value is replaced by the string, unless the string passed to it is preceded by "+" in which case it is appended to the existing string. The string passed may be represented by a literal string, as in "String.name John A. MacDonald", by an String variable, as in "String.name *String.name.firstPM", or by an Object variable, in which case the name of the Object will be used.
Object variables
One sort of variable contains a objects (such as modules, or windows). The command to define and assign values to an object variable is:- Object.[name] [reference to object]; — this declares an Object variable and assigns it the object indicated. If the variable already exists, its value is replaced. The reference may be "it", as in "Object.thisModule *It", or an Object variable, as in "Object.thisModule *Object.storedModule".
The variable "It"
Standard Mesquite Commands to modules return an object. In the scripting language, this returned object is stored in the variable "It". Thus, after a command "getNumberDataSets" to the FileCoordinator, the FileCoordinator returns an Integer variable containing the number of data sets. This can be stored in an Integer variable by following the command by "Integer.numDataSets *it". Likewise, "tell" often makes use of "it".Flow and command control
As noted above Mesquite's scripting language has flow control as well as control of the object to be commanded.Using "tell" to direct commands
Commands are directed toward commandable objects, including modules, windows, and others. Since different objects might use the same command names, the object to which a command is directed must be indicated. In the scripting language, at any point there is an implicit object being commanded. Subsequent commands are directed to a different object using the "tell" command, which must be balanced by "endTell". At the root level, the FileCoordinator is being commanded.Flow control
Flow control statements include "if", "for", and "while". Others are available (such as ifnot,stop, exitTell). Details on these can be found via the web page shown by selecting Scripting Commands from the Help menu while Mesquite is running.- if [integer or integer variable]; ... endIf; — the statements between if and endIf are executed if the integer variable is non-zero
- for [integer or integer variable]; ... endFor; — the statements between for and endFor are executed as many times as the initial value of the integer variable.
- while [integer or integer variable]; ... endWhile; — the statements between while and endWhile are executed repeatedly as long as the value of the integer variable is non-zero.
Debugging
There are a number of commands that are useful for debugging. For instance, if the Command "debug" is placed in the block, a debugging mode will be enabled which reports in the console more details about the commands as they are executed. More information about such commands can be found by selecting the "Scripting Commands" menu item under the Help menu when Mesquite is running.Examples
Here are three simple example scripts that you can send to the Tree window usingWindow>Scripting>Send Script. Open a tree window, turn on Trace Character History, and then paste one of these scripts into the Send Script dialog box. The first script scrolls from tree to tree, for each recording in a file "results.txt" the reconstruction of ancestral states.String.resultsFile 'results.txt'; saveMessageToFile *String.resultsFile 'RESULTS with different trees'; appendReturnToFile *String.resultsFile; getWindow; tell It; getNumTrees; Integer.numReps *It; ifNotCombinable *Integer.numReps; Integer.numReps 10; [in case indefinite number of trees] endIf; endTell; Integer.count 0; for *Integer.numReps; increment.count; getWindow; tell It; setTreeNumber *Integer.count; endTell; getEmployee #mesquite.ancstates.RecAncestralStates.RecAncestralStates; tell It; getLastResult; String.result *It; appendMessageToFile *String.resultsFile *String.result; appendReturnToFile *String.resultsFile; endTell; endFor;
This second script scrolls from character to character, for each recording the ancestral states in a file "results.txt":
String.resultsFile 'results.txt'; saveMessageToFile *String.resultsFile 'RESULTS with different characters'; appendReturnToFile *String.resultsFile; getEmployee #mesquite.ancstates.RecAncestralStates.RecAncestralStates; tell It; getNumHistories; Integer.numReps *It; ifNotCombinable *Integer.numReps; Integer.numReps 10; [in case indefinite number of characters] endIf; endTell; Integer.count 0; for *Integer.numReps; increment.count; getEmployee #mesquite.ancstates.TraceCharacterHistory.TraceCharacterHistory; tell It; setCharacter *Integer.count; endTell; getEmployee #mesquite.ancstates.RecAncestralStates.RecAncestralStates; tell It; getLastResult; String.result *It; appendMessageToFile *String.resultsFile *String.result; appendReturnToFile *String.resultsFile; endTell; endFor;
The third script scrolls from tree to tree, printing each one.
getWindow; tell It; getNumTrees; Integer.numReps *It; ifNotCombinable *Integer.numReps; Integer.numReps 10; [in case indefinite number of trees] endIf; endTell; for *Integer.numReps; getWindow; tell It; setTreeNumber *Integer.count; printToFit; endTell; endFor;