Architecture

Quills

Last updated: 

This page briefly discusses the design of the Quills library.

This is important if you plan to use Quills with your own interpreter, or if you will be maintaining the framework.  You may also just find it interesting.

Quills Framework

For flexibility, the API is a framework, with no embedded interpreter.  Unlike other applications that have script bindings, MacTerm does not always need to “run” first in order to execute script code, nor are your scripts constrained to a particular Python version (aside from the fact that MacTerm 4.x uses Python 2.x, and MacTerm 5.x now uses Python 3.x).

One advantage of this is that scripts are not gratuitously forced to run in MacTerm.  If the script never has to launch anything graphical (such as a terminal window), the user might not even know that MacTerm code was used. 

Sometimes, you have to use other Python code that has more stringent version requirements; for instance, you may wish to use a version of Python that is newer than the default on macOS, requiring you to build your own python.  This is another advantage of the framework approach: since you are not using an interpreter embedded in MacTerm, you are not forced to use a particular version of the interpreter.  Usually, you can just import Quills normally into your alternate interpreter version.

The framework is binary-compatible with multiple interpreter versions.  Of course, the source code is also available, if it should ever be necessary to rebuild the bindings for another interpreter; you may also contact the lead MacTerm developer to request binary wrappers that support a certain version of Python.

The directory structure of the framework (ignoring top-level symbolic links and resources) of MacTerm 5.x is:

MacTermQuills.framework/Versions/B/MacTermQuills
                                  /lib/
                                      /python3.9/_quills.so
                                                /quills.py
                                                /quills.pyc

The directory structure of the framework (ignoring top-level symbolic links and resources) of MacTerm 4.x is:

MacTermQuills.framework/Versions/B/MacTermQuills
                                  /lib/
                                      /python2.6/_quills.so
                                                /quills.py
                                                /quills.pyc

More on the structure above:

Note:  See the importing page for examples of how to import the Quills module from Python.  It should be obvious from the above that the correct library paths will vary significantly based on the OS and architecture, and that importing is non-trivial.  However, the code to do importing correctly is already written (used in the MacTerm main entry point), and you are encouraged to simply do what it does, in your own scripts.

PyMacTerm Framework

Note that MacTerm also includes the PyMacTerm framework.  This is not part of Quills; PyMacTerm actually depends on Quills, and implements MacTerm features entirely in Python!

You can use the PyMacTerm framework as a source of good examples on how to use Quills successfully.  You can also use it to restore default behavior (the main MacTerm script in the application bundle shows you which PyMacTerm functions are registered).

API Design

The Quills API is currently quite simple, and there are many plans to improve it.  It is split into domains, similar to those seen elsewhere in MacTerm; for instance, Session deals with running commands, and Terminal customizes the emulator.

Quills is heavily callback based.  It is almost never correct to look for a global function to sequentially perform an action; instead, there will be a way to request that the C++ implementation invoke one or more Python functions at key points.  This is the basis for customization, and can be extremely powerful even with the small API that is available so far!

The primary reason for callbacks is that you often will run the main event loop (launching the MacTerm graphical user interface), and doing so will prevent Python from returning normally to your script.  Without callbacks, you would be forced to perform every possible Python action up front, which is simply not useful.

Quills callbacks will not incur performance penalties by invoking Python code in critical places.  Some callbacks will be called earlier than you might think, so that results can be cached for good performance when the information is actually required.

The Quills API is carefully edited to provide only unique functionality.  You will not see pointless routines that are wrappers for something else. For instance, there are other ways to access MacTerm low-level settings (such as the defaults program), so there are no Quills APIs to duplicate that; on the other hand, there are Preferences APIs for accessing and changing things that would be cumbersome to find using defaults, such as the names of available collections.

Also, the API is not designed to cover things that are appropriately handled using data files.  For example, the “factory defaults” in MacTerm are defined by DefaultPreferences.plist in its main bundle, which is a much more convenient way to make initial settings.

In general, exceptions flow gracefully across Python and C++ boundaries in either direction; so you may catch standard exception types in your Python code to find problems, and raise errors that you want Quills to know about.  This is a key debugging feature.  However, it has a cost; in the future, some APIs may choose to forego exception handling for performance reasons, and will be documented as such.

Bindings

MacTerm uses the Simple Wrapper Interface Generator (SWIG) to generate its bindings between C++ code and either Python 2.x (on macOS 10.14 or earlier with MacTerm 4.x) or Python 3.x (on macOS 10.15 or later with MacTerm 5.x).

SWIG supports a variety of scripting languages from one source.  Only a small amount of Python-specific code is currently required, so an interesting project would be to add support for additional languages someday.

The advantages of bindings are clear.  MacTerm features can be implemented entirely in Python, taking advantage of the whole Python standard library.  Code is easier to write, test, debug, and maintain in Python than in C++.  Advanced users can perform unheard-of customizations.