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.

Bindings

MacTelnet uses the Simple Wrapper Interface Generator (SWIG) to generate its bindings between Python and C++ code.

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.  MacTelnet 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.

A downside, of course, is that building the application is far from trivial!

Framework

In order to be most useful, the API is in a standalone framework and not an embedded interpreter.  As a framework, Quills can be imported into any interpreter that is binary-compatible; one does not need the exact interpreter used by the author of Quills.

The framework contains a few different builds, to maximize the chances of binary compatibility.  Of course, the source code is also available, if it should ever be necessary to rebuild the SWIG wrappers for another interpreter.

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

Quills.framework/Versions/A/Quills_debug
                           /lib/
                               /python2.3/_Quills.so
                                         /Quills.py
Quills.framework/Versions/B/Quills_debug
                           /lib/
                               /python2.3.5/_Quills.so
                                           /Quills.py
                               /python2.5/_Quills.so
                                         /Quills.py

More on the structure above:

API

The Quills API is currently quite simple, and there are many plans to improve it.

Currently there are a few different classes that separate functionality into clear domains: for example, Sessions manage command components, whereas Terminals control emulation, much like in the MacTelnet application itself.

Care is taken not to add unnecessary functionality; you will not see APIs that simply act like wrappers for something else.  For instance, there are ways to manipulate any application’s preferences already (the defaults tool, among others), so Quills does not provide primitives simply to tweak MacTelnet settings.  The goal is to provide an API with essential parts to access or control things that are unique to MacTelnet.

Also, the API is not designed to cover things that are appropriately handled using data files.  For example, the “factory defaults” in MacTelnet are defined by DefaultPreferences.plist in its main bundle, and sometimes it is better to just customize that file instead of trying to write a script.

Callbacks are used frequently, and often the only purpose of a global function (as opposed to an object-oriented method) is to manage a callback for an event.  The register-then-call-back approach allows MacTelnet to invoke user code even during loops that would otherwise suspend a script; the main loop is the graphical user interface itself.  Since calling Python from C++ is not fast, Quills designs callbacks in ways that encourage calls to be either infrequent, or timed appropriately; for instance, the purpose of a callback may be to return data “up front”, that can be cached for later use during performance-critical loops.

Exceptions are propagated in some APIs, allowing Python code to catch errors that originated in C++, or vice-versa.  Since propagation adds extra code and reduces performance, it is not universal; an API must eventually execute a callback, or be a C++ function that commonly throws exceptions, to justify the extra layer.  Still, this is a key debugging feature.