Bob Ippolito (@etrepum) on Haskell, Python, Erlang, JavaScript, etc.

graphviz on the Mac, LaunchServices


For an excellent example of what it takes to do a great port of UNIX software to OS X, look no further than Glen Low's port of graphviz for OS X. For good reasons to take a look at graphviz, check out John Schull's Weblog, especially entries such as Outline to graphviz.

This port and GUI front-end adds features such as rendering via the native OS X APIs (finally, good fast PDFs!), and it can actually use kqueue on OS X 10.3 to automatically re-render when a file changes!

Not only does at add a slew of cool new features (most of which I didn't mention), but all of the CLI tools are wrapped up in a nice little application bundle; making installation and removal painless, and relocation no longer becomes a problem.

If all OS X with significant UNIX heritage were distributed this way, one might think it would become difficult to add them all to your PATH. Fortunately, that's just not true [1]. Here's a nice little snippet of LaunchServices [2] code that will return the full path of an application by name regardless of where it happens to be or if it has moved [3]:

from LaunchServices.Launch import LSFindApplicationForInfo
from Carbon.CoreFoundation import kCFURLPOSIXPathStyle
# this would normally come from LaunchServices.LaunchServices
# but bgen isn't perfect, and doesn't make this constant a
# four char code.
kLSUnknownCreator = '\x00\x00\x00\x00'

def pathForName(appName):
    Returns the file system path for an application by name.
    NOTE: You should include the suffix, as in "".
    fsRef, cfURL = LSFindApplicationForInfo(kLSUnknownCreator, None, appName)
    return cfURL.CFURLCopyFileSystemPath(kCFURLPOSIXPathStyle).toPython()

if __name__ == '__main__':
    import sys
    if len(sys.argv) == 1:
        import os
        raise SystemExit, "Usage: %s" % (
    for appName in sys.argv[1:]:
        print pathForName(appName.decode('utf8')).encode('utf8')

Usage looks like this (note: I use tcsh not bash):

[crack:~/bin] bob% setenv PATH ${PATH}:`./`/Contents/MacOS
[crack:~/bin] bob% rehash
[crack:~/bin] bob% which dot
[crack:~/bin] bob% dot --help
dot: option -- unrecognized

Usage: dot [-Vv?] [-(GNE)name=val] [-(Tlso)<val>] <dot files>
 -V          - Print version and exit
 -v          - Enable verbose mode
 -Gname=val  - Set graph attribute 'name' to 'val'
 -Nname=val  - Set node attribute 'name' to 'val'
 -Ename=val  - Set edge attribute 'name' to 'val'
 -Tv         - Set output format to 'v'
 -lv         - Use external library 'v'
 -ofile      - Write output to 'file'
 -q[l]       - Set level of message suppression (=1)
 -s[v]       - Scale input by 'v' (=72)
 -y          - Invert y coordinate in output
[1]Well, so long as application developers can agree on where those binaries should go in a bundle. The dust hasn't quite settled on this issue yet, it's a toss-up between MacOS, Resources, and arbitrary subfolders of Resources (probably mirroring a "usr prefix", like Resources/bin, Resources/lib, etc.)
[2]This code for LaunchServices from Python 2.4, not my LaunchServices wrapper. It is backwards compatible to Python 2.3, but is not yet available from a PackageManager repository.
[3]There are some limitations to the power of Launch Services. For example, The application must have been launched via the Launch Services API (i.e. through Finder) at least once since it has been on that volume, or it must reside in a standard Applications folder such as /Applications or /Network/Applications.
blog comments powered by Disqus