Starkits and Starpacks
Tcl scripts are typically interpreted by the Tcl shell, tclsh.
Starkits are single files that contain a virtual filesystem populated with Tcl scripts and other files. Starkits are interpreted by Tclkits, which are single files containing entire Tcl installations.
A Starpack is a Starkit joined to a Tclkit. It is a single executable file that contains a script and the interpreter needed to run it.
Why
Packaging Tcl/Tk scripts as Starkits is advantageous because it allows the set of files that comprise an application to be distributed and installed as a single file.
Likewise, Tclkits allow the complex set of files that comprise a Tcl/Tk installation to be managed as a single file. This confers advantages to the developer (easily test against different interpreter releases) and to the user (installation is simple—as is uninstallation).
Because they have no prerequisites, Starpacks are ideal for distribution to casual users. Since each Starpack contains a generic Tclkit, expert users may opt to save space by installing a single Tclkit to execute individual application Starkits.
How
Tclkits come in many flavors. You can run a Starkit with a Tclkit of your choice by calling it explicitly:
./tclkit-darwin-univ Starkit.kit
If a Tclkit is installed as tclkit
, it is used implicitly when you execute a Starkit directly:
cp tclkit-darwin-univ /usr/local/bin/tclkit
./Starkit.kit
Starpacks are executed directly:
./Starpack
The leading ./
characters imply that the executable is in the current directory.
Starkits and Starpacks can be created and disassembled with SDX, itself a Starkit. You can use SDX to create a Starpack of itself.
Posted on Monday, October 1st, 2007.
LDMerge
LDMerge is a console program for merging and unmerging the contents of multiple LDraw directory structures. Multiple part libraries may be used to manage unofficial parts. Some LDraw-compatible programs support this concept, but others do not. By providing a way to temporarily combine several part libraries, this utility facilitates those tools which recognize only a single LDraw directory — without forcing you to adopt the same convention.
Unlike its predecessor LDLink, LDMerge does not rely on symbolic links.
Overview
LDMerge performs two actions: merge
and unmerge
.
Both actions are applied to a target
library. Merging adds parts to the target libary and unmerging removes those parts from the target library.
A source
library provides the parts that are merged. More than one source can be merged into the same target. Parts from all sources or from one particular source can be unmerged.
An index
file records the source of each merged part.
LDMerge considers an LDraw part library to be a directory containing the subdirectories parts
, parts/s
, p
, and p/48
. Files of any type within these subdirectories are considered parts. Other files and folders in the library but not within those specific subdirectories are ignored.
Usage
LDMerge accepts command line arguments corresponding to each of the key terms introduced above.
ldmerge merge|unmerge
[-target LDRAWDIR]
[-source LDRAWDIR|all]
[-index FILE]
[-mode copy|move|link]
[-flag verbose]
The first argument is required and specifies the action: merge
or unmerge
.
If -target
is not specified, the value of the LDRAWDIR
environment variable is used.
If -source
is not specified, it is assumed to be the target’s Unofficial
subdirectory. (LDView can automatically download unofficial parts to this location.) The special all
source is meaningful only when unmerging.
If -index
is not specified, the file ldmerge.ldr
in the target directory is used.
The -mode
option allows you to specify how parts are merged. By default, part files are copied from the source to the target. If the move
mode is used, the source files themselves are moved to the target directory. They are returned to the source directory when unmerged. The link
mode creates symbolic links (on Mac OS X or Linux) or hard links (on Windows NTFS systems) to the source files. Moving or linking can be faster, but copying may present the fewest complications. It is not necessary to specify a -mode
when unmerging.
Specify -flag verbose
to print a line reporting each merge or unmerge operation.
Examples
To merge unofficial parts downloaded by LDView with the rest of your part library, the following command may be sufficient:
ldmerge merge
That copies each part from LDRAWDIR/Unofficial
to the corresponding subdirectories of LDRAWDIR
, and records the merges in LDRAWDIR/ldmerge.ldr
. The default merge can be reversed with the following command:
ldmerge unmerge
This merge command moves the actual source files to the target library:
ldmerge merge -source /Custom/LDraw -mode move
To restore the main library to its original state by removing all merged files, use this command (merged files will be returned to their original location if they were moved):
ldmerge unmerge -source all
A merge can be made permanent by removing the relevant lines from the merge index.
LDMerge will not replace parts in the target library unless the existing merge index indicates that they came from the current source library. Part conflicts and other recognized exceptions are reported; merging or unmerging proceeds if possible. Errors copying, moving, or linking files cause the program to stop.
Parts are identified by filename. File contents are not examined.
Index Format
The index records are formatted as LDraw meta commands. At present, this choice of format is arbitrary, but it may facilitate interesting possibilities in the future.
0 !LDMERGE /Target/LDraw/parts/part.dat SOURCE /Source/LDraw/parts/part.dat
Each record documents a merged part in the target library. The path to this part is stated after the !LDMERGE
token. Subsequent parameters may appear in any order and are interpreted as “PARAMETER value
” pairs. Presently only the SOURCE
parameter is used; it specifies the part’s original location.
Records without SOURCE
parameters are valid. They effectively “reserve” parts. If present, the part cannot be removed; if not present, the part cannot be added.
Index lines that cannot be interpreted as LDMerge records are ignored but retained. They appear before all valid records in the updated index.
Paths may contain whitespace, in which case they appear quoted in curly braces.
Downloads
LDMerge is currently available in three forms:
- ldmerge-1.1-kit.zip 8KB
Platform independent Starkit. Requires a separate Tclkit (8.4.16 recommended). - ldmerge-1.1-mac.zip 1.8MB
Self-contained universal binary (x86 + PPC) for Mac OS X 10.4+. - ldmerge-1.1-win.zip 553KB
Self-contained console binary for Windows.
The source code may be extracted from any version with SDX. LDMerge is localizable; see en_us_example.msg for more details. LDMerge is public domain software.
Changes
1.1:
- Fixed
-source all
- Index and most messages use native directory separators
Posted on Monday, October 1st, 2007.
Laundromat Sidewalk Math
Doing laundry is never a chore when you go to the Super-X Launderette.
You might even learn something.
Posted on Tuesday, September 25th, 2007.
Resize Pages Graphics by Percent
Conspicuously missing from Pages is the ability to resize graphic objects by specific percents. You can say “make this graphic 2 inches wide,” but you can’t say “make this graphic 30% of its current width.” So, I wrote a script to do the dirty work for me:
It resizes the selected graphics by the specified percentage.
Download
The “Recenter” version keeps the graphic centered at its original location, which I have found to be rather annoying. Install in ~/Library/Scripts/Applications/Pages/
and invoke with a script runner of your choice.
Notes
Unfortunately, there doesn’t seem to be a scriptable way to determine whether the “Constrain proportions” options is set for a particular graphic. Since this option affects size changes, I simply make the assumption that it is set. It is, by default, for inserted images. For other graphics, check the “Metrics” inspector pane:
Note that images are not necessarily inserted at their original size. If you want to resize an image to some percentage of its actual size, be sure to click the “Original Size” button before resizing.
This script was written with Pages’ page layout mode in mind, but it should work with graphics in word processing mode, too. There are some rough edges, particularly where handling different sorts of selections is concerned.
Here is a video demonstration.
Posted on Monday, September 24th, 2007.
Script Runners
Rather than repeatedly explaining how to launch each script I write, I’m going to use this post as a generic reference.
AppleScripts are typically little bits of code that extend or connect your applications’ functionality. As such, they are not necessarily invoked like normal programs. Many fine utilities exist which provide convenient ways to run scripts, including LaunchBar, Butler, QuickSilver, Keyboard Maestro, and others. Here, however, I will describe the simple script runners I prefer, one of which is even built in to Mac OS X.
FastScripts
I favor FastScripts, which utilizes the same script folders as Apple’s Script Menu. It features assignable keyboard shortcuts, which help scripts act more like natural extensions of your applications. The FastScripts menu looks like this:
Apple Script Menu
To enable the Script Menu, open “AppleScript Utility” (in /Applications/AppleScript/
) and check the “Show Script Menu in menu bar” option:
A “script” icon will appear in your menu bar. It displays a menu like this:
Scripts stored in ~/Library/Scripts/Applications/Finder/
are listed in the “Finder Scripts” section, which appears only when the Finder is the frontmost application. Scripts stored in ~/Library/Scripts/
are listed in the unlabeled section and are always accessible. You can create application subfolders for application-specific scripts yourself. The tilde (~
) represents your home folder.
Here is a video demonstration of the Script Menu.
Posted on Monday, September 24th, 2007.
User Interfaces for ThisService Scripts
ThisService is arguably — and appropriately — oriented towards text-processing and other “faceless” services. However, with a little imagination it is easy to conjure uses for the basic user interaction capabilities provided by AppleScript’s Standard Additions. Incorporating these commands in a service script requires a little extra sleight of hand, as it may be necessary to direct them to whatever application is active when the service is invoked.
I have had variable success in my various attempts to do this. Here is a summary of how it can be done, combined with an account of problems I’ve encountered with certain configurations.
Finding the Name of the Frontmost Application
In his example word count service, John Gruber demonstrates how to use System Events to determine the name of the frontmost application:
tell application "System Events" set _app to the name of the first process whose frontmost is true end tell
Another method discussed at the Mac OS X Hints forums also seems to work, even though the .app
extension is included in the application name:
set _app to name of (info for (path to frontmost application))
Now the frontmost application (whatever it may be) can be directed to do something.
tell application _app to ...
This is useful because we don’t necessarily know what application will be active when our service is invoked.
An Interface for Input Services: A Predictable Success
ThisService can generate three kinds of scripts: those that act on input, those that produce output, and those that do both (filters). Gruber’s word count service acts on input, as does my save selection service. The AppleScript that powers these kinds of services looks something like this:
on process(_input) tell application "System Events" to set _app to the name of the first process whose frontmost is true tell application _app to display alert "Input: " & _input end process
When text is selected and the service is invoked, an alert reports the selected text.
An Interface for Output Services: A Disappointing Surprise
Here is the code for a simple service that produces output. Via the same mechanism as the input service, it attempts to report the name of the frontmost application and return that string:
on process() tell application "System Events" to set _app to the name of the first process whose frontmost is true tell application _app to display alert "Output: " & _app return _app end process
Running this service causes the frontmost application to become unresponsive for an abnormal length of time. Spin Control indicates that the service executable also hangs, although I don’t know how to interpret the report. The alert subsequently appears, but the service fails: the script’s return value is not inserted. A relevant message appears in the Console:
2007-09-12 21:45:28.621 TextEdit[2721] tossing reply message sequence 9 on thread 0x306a90
An Interface for Filter Services: Beating a Dead Horse
This filter service exhibits the same problem as the example output service.
on process(_input) tell application "System Events" to set _app to the name of the first process whose frontmost is true tell application _app to display alert "Input: " & _input & return & "Output: " & _app return _app end process
The alert appears after an extended delay, but no output is received.
An Interface Compromise for Output and Filter Services
One way to circumvent the issue is to tell System Events to display the interface itself. This eliminates the delay and allows the service to return successfully.
on process() tell application "System Events" set _app to the name of the first process whose frontmost is true activate display alert "Output: " & _app end tell return _app end process
Unfortunately, this approach has the unresolved side effect of stealing focus from the frontmost application’s windows. Explicitly restoring focus would require something like tell application _app to activate
, which exhibits the problematic behavior.
on process(_input) tell application "System Events" set _app to the name of the first process whose frontmost is true activate display alert "Input: " & _input & return & "Output: " & _app end tell return _app end process
Without activate
, System Event’s alert wouldn’t receive focus, either.
Inconclusive Conclusions
The method described above allows output and filter services to present simple user interfaces, but it is an imperfect and somewhat annoying solution. If you want to provide a robust interface for an arbitrary service, you should probably write the service from scratch. On the other hand, tell application _app
’s compatibility with input services suggests the same idiom ought to be possible with the other kinds of services produced by ThisService.
It is also possible that I have overlooked or misunderstood some aspect of AppleScript, services, or Mac OS X which explains the perceived discrepancy between service types. Elucidation is welcome.
Posted on Wednesday, September 12th, 2007.
Tcl:: Language Module
TextWrangler includes basic support for Tcl syntax highlighting and procedure recognition. Procedures are listed in a popup menu that makes it easy to navigate around large files. Unfortunately, this mechanism does not recognize procedures defined in a namespace with the concise namespace::procedure
syntax. The intervening “::
” characters are not among those TextWrangler normally recognizes as valid identifier characters.
For example, “Bar” appears in the function menu when a procedure is defined like this:
namespace eval Foo { proc Bar {} { puts "Hello, World!" } }
I prefer this syntax:
proc Foo::Bar {} { puts "Hello, World!" }
but procedures defined this way don’t appear in the function menu:
Rather than forfeit the convenience of the function popup or conform to a more cumbersome convention, I created a codeless language module that essentially duplicates TextWrangler’s built-in Tcl support. The only distinction is that I’ve added the colon to the “Identifier and Keyword Characters” string. As a result, TextWrangler recognizes strings containing colons as valid procedure names:
Unfortunately, this approach appears to prevent recognition of the nested syntax. Ideally I would like to support both styles.
Download
I believe this module should be compatible with BBEdit as well as TextWrangler.
Tcl:: Codeless Language Module 1k .zip
Installation
Put Tcl.plist
in ~/Library/Application Support/TextWrangler/Language Modules/
and restart TextWrangler. The module identifies itself as “Tcl::” to differentiate it from TextWrangler’s internal “Tcl” mode, which remains available. It may be necessary to tweak your language preferences to use this module by default, as shown here.
Please share any problems you encounter with this module, particularly those related to the colon’s promoted status.
Posted on Thursday, September 6th, 2007.
Tcl starting points for ThisService
Waffle Software has released ThisService 2.0, a utility that bundles simple scripts as services accessible throughout Mac OS X. Jesper has included example scripts written in a variety of popular languages to help service authors get started. I like Tcl (have you met?), so I have prepared similar templates for Tcl programmers.
Download Tcl service starting points 2.3K
Tcl supports Unicode intrinsically, so, as with the other starting points, these templates should handle whatever text you throw at them without complaint.
Posted on Thursday, September 6th, 2007.
Yojimbo Script Updates
Minor updates to a few Yojimbo AppleScripts are available: Export with Comment Tags, Archive Bookmarks, and Backdate Items. These updates allow the scripts to act on items even when Yojimbo’s item list pane does not have input focus. This means you can run the scripts when editing items as well as when selecting them.
Posted on Friday, August 31st, 2007.
Yojimbo Export with Comment Tags
In response to this query, I wrote a script which allows Yojimbo items to be exported with their tags preserved as Spotlight comments. I don’t really use Spotlight, so I’m not sure if the comment format is ideal, but it works fine for a first draft.
Now updated for compatibility with Mac OS X 10.5.
Download ExportwithCommentTags.scpt.zip 1.2 4k
Put the script in ~/Library/Scripts/Applications/Yojimbo
, select some items, and choose “Export with Comment Tags” from your script menu.
Posted on Thursday, August 30th, 2007.