Archive for the 'Toolchain' Category

Adding Growl support to Mercurial

Wednesday, April 1st, 2009

Add this to your ~/.hgrc file:

[hooks]
changegroup.growl = ((echo "$HG_URL" | grep -vF 'file:' > /dev/null) && growlnotify -n Mercurial 'Pull successful' -m "Pulled at least $(hg log --template='\n' -r$HG_NODE:tip | wc -l | sed -e 's/ //g') changesets from $HG_URL") || true
outgoing.growl = (test "x$HG_URL" '!=' 'x' && growlnotify -n Mercurial 'Push successful' -m "Pushed to $HG_URL") || true

It’s not perfect: Both notifications share the same notification name. As long as that isn’t a problem, this works fine.

Manpage Monday: afconvert(1)

Monday, November 24th, 2008

afconvert is a command-line utility that uses Core Audio to convert audio files from one format to another.

The manpage is actually pretty sparse (like the rest of the Core Audio documentation); you’re better off reading the command-line help:

% afconvert -h

A simple example is to convert an AIFF file to AAC in an MPEG-4 file:

% afconvert -f 'm4af' -d 'aac ' -b 98304 Recording.aiff

The resulting file is 96 kbps (98,304 bps) and is named “Recording.m4a”.

UPDATE 2008-11-26: Corrected permalink and title to include the section number.

New service: Insert Mac OS X Build Number

Friday, November 21st, 2008

File: InsertMacOSXBuildNumber.zip

A service that inserts the build number (for example, 9F33) of your current Mac OS X installation.

The main purpose for this is so that, when filing bugs in Radar, you can precisely specify which build of Mac OS X you’re running. (Especially if you’re running a pre-release build of a future version of Mac OS X.)

I created it with ThisService, of course.

(And yes, I am also working on tonight’s Framework Friday post.)

New Mercurial extension: Bitbucket Extension

Monday, September 29th, 2008

If you’ve ever used Bazaar, you know that one of its features is a shorthand URL scheme for referring to Bazaar repositories on Launchpad. For example, to clone Sparkle’s main repository:

% bzr clone lp:sparkle

I’ve created an extension that enables you to do the same thing in Mercurial with Bitbucket repositories.

% hg clone bb://boredzo/bitbucketextension

The Bitbucket extension adds two URL schemes: bb:, and bb+ssh:. The bb: scheme communicates with Bitbucket over HTTPS; you can guess what bb+ssh does.

(Note: As of Mercurial 1.0.2, you must include the double-slash, because hg pull will interpret the URL as a relative path without it.)

svn merging is full of fail

Thursday, August 7th, 2008

[SCENE: adium/branches/summer_of_code_2008/unit_testing. I’m merging changes from trunk to this branch.]

svn --version
svn, version 1.5.0 (r31699)
   compiled Jul  5 2008, 04:29:25

Copyright (C) 2000-2008 CollabNet.
Subversion is open source software, see http://subversion.tigris.org/
This product includes software developed by CollabNet (http://www.Collab.Net/).

The following repository access (RA) modules are available:

* ra_neon : Module for accessing a repository via WebDAV protocol using Neon.
  - handles 'http' scheme
  - handles 'https' scheme
* ra_svn : Module for accessing a repository using the svn network protocol.
  - with Cyrus SASL authentication
  - handles 'svn' scheme
* ra_local : Module for accessing a repository on local disk.
  - handles 'file' scheme
* ra_serf : Module for accessing a repository via WebDAV protocol using serf.
  - handles 'http' scheme
  - handles 'https' scheme

___
svn up
At revision 24694.
___
svn st
?      Docs
?      DoxyCleaned
~      Resources/Message Styles/renkooNaked.AdiumMessageStyle/Contents/Resources/Incoming/buddy_icon.png
~      Resources/Message Styles/renkooNaked.AdiumMessageStyle/Contents/Resources/outgoing_icon.png
~      Resources/Message Styles/renkooNaked.AdiumMessageStyle/Contents/Resources/Outgoing/buddy_icon.png
~      Resources/Message Styles/renkooNaked.AdiumMessageStyle/Contents/Resources/incoming_icon.png

(~ = “versioned item obstructed by some item of a different kind”)

rm -Rf Resources/Message\ Styles/renkooNaked.AdiumMessageStyle
___
svn up Resources/Message\ Styles/renkooNaked.AdiumMessageStyle
[snip]
___
cd ../../..
___
svn merge -r23936:24694 trunk branches/summer_of_code_2008/unit_testing 
--- Merging r23937 through r24694 into 'branches/summer_of_code_2008/unit_testing':
[snip]
Conflict discovered in 'branches/summer_of_code_2008/unit_testing/Resources/Emoticons/MSN.AdiumEmoticonset/Emoticons.plist'.
Select: (p) postpone, (df) diff-full, (e) edit,
        (h) help for more options: e
[I correct the plist to match what trunk has.]
Select: (p) postpone, (df) diff-full, (e) edit, (r) resolved,
        (h) help for more options: r
___
plutil -lint branches/summer_of_code_2008/unit_testing/Resources/Emoticons/MSN.AdiumEmoticonset/Emoticons.plist 
branches/summer_of_code_2008/unit_testing/Resources/Emoticons/MSN.AdiumEmoticonset/Emoticons.plist: OK
___
plutil -lint branches/summer_of_code_2008/unit_testing/Adium.xcodeproj 
branches/summer_of_code_2008/unit_testing/Adium.xcodeproj: file does not exist or is not readable or is not a regular file
___
plutil -lint branches/summer_of_code_2008/unit_testing/Adium.xcodeproj/project.pbxproj 
branches/summer_of_code_2008/unit_testing/Adium.xcodeproj/project.pbxproj: OK
___
plutil -lint branches/summer_of_code_2008/unit_testing/Frameworks/AIUtilities\ Framework/AIUtilities.framework.xcodeproj/project.pbxproj
branches/summer_of_code_2008/unit_testing/Frameworks/AIUtilities Framework/AIUtilities.framework.xcodeproj/project.pbxproj: OK
___
cd branches/summer_of_code_2008/unit_testing   %~/Projects/@otherpeoplesprojects/adium(0)
___
xcodebuild -configuration Development       
[snip]
** BUILD SUCCEEDED **
___
svn ci
Sending        unit_testing
[snip]
Transmitting file data .......................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................svn: Commit failed (details follow):
svn: Checksum mismatch for '/Volumes/Home-etc/Users/prh/Projects/@otherpeoplesprojects/adium/branches/summer_of_code_2008/unit_testing/Resources/Emoticons/MSN.AdiumEmoticonset/.svn/text-base/Emoticons.plist.svn-base'; expected: '109d361ce47a49a66e53e98412251398', actual: 'a21c8336d1f839b9aaa8a2795a7e25f5'
svn: Your commit message was left in a temporary file:
svn:    '/Volumes/Home-etc/Users/prh/Projects/@otherpeoplesprojects/adium/branches/summer_of_code_2008/svn-commit.2.tmp'
___
cd Resources/Emoticons
___
rm -Rf MSN.AdiumEmoticonset 
___
svn up MSN.AdiumEmoticonset
A    MSN.AdiumEmoticonset
[snip]
Updated to revision 24694.
___
cd ../.. 
___
cd ../../..
___
svn merge -r23936:24694 trunk branches/summer_of_code_2008/unit_testing
svn: Working copy path 'Frameworks/Adium Framework/Source/AISharedAdium.h' does not exist in repository
___
svn merge -r23936:24694 trunk/Resources/Emoticons branches/summer_of_code_2008/unit_testing/Resources/Emoticons 
___
svn merge -r23936:24694 trunk/Resources/Emoticons/MSN.AdiumEmoticonset branches/summer_of_code_2008/unit_testing/Resources/Emoticons/MSN.AdiumEmoticonset 
___
svn merge -r23936:24694 trunk/Resources/Emoticons/MSN.AdiumEmoticonset/Emoticons.plist  branches/summer_of_code_2008/unit_testing/Resources/Emoticons/MSN.AdiumEmoticonset/Emoticons.plist
___
cd branches/summer_of_code_2008/unit_testing/Resources/Emoticons/MSN.AdiumEmoticonset 
___
svn pl Emoticons.plist 
___
svn st
___
popd
___
diff -u trunk/Resources/Emoticons/MSN.AdiumEmoticonset/Emoticons.plist branches/summer_of_code_2008/unit_testing/Resources/Emoticons/MSN.AdiumEmoticonset/Emoticons.plist 
--- trunk/Resources/Emoticons/MSN.AdiumEmoticonset/Emoticons.plist       2008-06-26 20:50:51.000000000 -0700
+++ branches/summer_of_code_2008/unit_testing/Resources/Emoticons/MSN.AdiumEmoticonset/Emoticons.plist  2008-08-07 11:20:27.000000000 -0700
@@ -367,7 +367,7 @@
                        Name
                        Cool
                
-               I don't know.gif
+               I Don't Know.gif
                
                        Equivalents
                        
[Um, OK. What happened to all the differences it had from trunk that I had merged over? Suddenly, almost all the differences are gone, and the files are nearly the same!]
___
diff -u trunk/Resources/Emoticons/MSN.AdiumEmoticonset/Emoticons.plist branches/summer_of_code_2008/unit_testing/Resources/Emoticons/MSN.AdiumEmoticonset/Emoticons.plist
___
cd branches/summer_of_code_2008                %~/Projects/@otherpeoplesprojects/adium(0)
___
svn ci unit_testing 
Sending        unit_testing
Transmitting file data ................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................svn: Commit succeeded, but other errors follow:
svn: Error bumping revisions post-commit (details follow):
svn: Directory '/Volumes/Home-etc/Users/prh/Projects/@otherpeoplesprojects/adium/branches/summer_of_code_2008/unit_testing/Frameworks/Sparkle.framework/Versions/A/Resources/ja.lproj/SUUpdateAlert.nib' is missing
svn: Directory '/Volumes/Home-etc/Users/prh/Projects/@otherpeoplesprojects/adium/branches/summer_of_code_2008/unit_testing/Frameworks/Sparkle.framework/Versions/A/Resources/ja.lproj/SUUpdateAlert.nib' is missing
svn: Your commit message was left in a temporary file:
svn:    '/Volumes/Home-etc/Users/prh/Projects/@otherpeoplesprojects/adium/branches/summer_of_code_2008/svn-commit.3.tmp'
___
svn st            %~/Projects/@otherpeoplesprojects/adium/branches/summer_of_code_2008(1)
?      svn-commit.2.tmp
?      svn-commit.tmp
?      svn-commit.3.tmp
 ML    unit_testing
?      unit_testing/Docs
?      unit_testing/DoxyCleaned
  L    unit_testing/Release
  L    unit_testing/Release/openUp
  L    unit_testing/Release/Artwork
M      unit_testing/Release/Makefile
  L    unit_testing/Frameworks
  L    unit_testing/Frameworks/libgmodule.framework
  L    unit_testing/Frameworks/libgmodule.framework/Versions
[snip]

(L in third column = Locked)
___
svn cleanup       %~/Projects/@otherpeoplesprojects/adium/branches/summer_of_code_2008(0)
svn: In directory 'unit_testing/Frameworks/JSON Framework/Tests/json.org'
svn: Error processing command 'committed' in 'unit_testing/Frameworks/JSON Framework/Tests/json.org'
svn: Working copy 'unit_testing/Frameworks/JSON Framework/Tests' locked
svn: run 'svn cleanup' to remove locks (type 'svn help cleanup' for details)
___
svn cleanup       %~/Projects/@otherpeoplesprojects/adium/branches/summer_of_code_2008(1)
svn: In directory 'unit_testing/Frameworks/JSON Framework/Tests/json.org'
svn: Error processing command 'committed' in 'unit_testing/Frameworks/JSON Framework/Tests/json.org'
svn: Working copy 'unit_testing/Frameworks/JSON Framework/Tests' locked
svn: run 'svn cleanup' to remove locks (type 'svn help cleanup' for details)
[So I get to manually clear the locks. Yay!]
___
tar cjf unit_testing.tbz unit_testing 
___
cd unit_testing   %~/Projects/@otherpeoplesprojects/adium/branches/summer_of_code_2008(0)
___
svn st | grep -Fw L | head
  L    Frameworks
  L    Frameworks/JSON Framework
  L    Frameworks/JSON Framework/Tests
A L+   Frameworks/JSON Framework/Tests/json.org
A L+   Frameworks/JSON Framework/Tests/types
A L+   Frameworks/JSON Framework/Tests/format
A L+   Frameworks/JSON Framework/Tests/rfc4627
  L    Frameworks/JSON Framework/Tests/Examples
  L    Frameworks/JSON Framework/Tests/Examples/JSONChecker
A L+   Frameworks/JSON Framework/Tests/jsonchecker
svn: Write error: Broken pipe
___
ls Frameworks/.svn 
dir-prop-base format          prop-base/      text-base/
entries         lock            props/          tmp/
___
ls Frameworks/.svn/lock 
Frameworks/.svn/lock
___
rm Frameworks/.svn/lock 
___
svn st -N Frameworks 
  L    Frameworks/JSON Framework
  L    Frameworks/libintl.framework
  L    Frameworks/libpurple.framework
  L    Frameworks/libmeanwhile.framework
  L    Frameworks/LMX.framework
R L+   Frameworks/Sparkle.framework
  L    Frameworks/AutoHyperlinks Framework
  L    Frameworks/libgobject.framework
  L    Frameworks/OTR.framework
  L    Frameworks/libglib.framework
  L    Frameworks/ShortcutRecorder
  L    Frameworks/libgthread.framework
  L    Frameworks/AIUtilities Framework
  L    Frameworks/OCMock.framework
  L    Frameworks/PSMTabBarControl.framework
  L    Frameworks/RBSplitView
  L    Frameworks/Growl-WithInstaller.framework
  L    Frameworks/Adium Framework
___
svn st -N Frameworks/JSON\ Framework 
  L    Frameworks/JSON Framework
A  +   Frameworks/JSON Framework/dmg.sh
  L    Frameworks/JSON Framework/Tests
  L    Frameworks/JSON Framework/Site
  L    Frameworks/JSON Framework/JSON.xcodeproj
  L    Frameworks/JSON Framework/Docs
A  +   Frameworks/JSON Framework/bench.m
M      Frameworks/JSON Framework/JSON-Info.plist
M      Frameworks/JSON Framework/CREDITS
___
rm Frameworks/JSON\ Framework/.svn/lock 
___
svn st -N Frameworks/JSON\ Framework
A  +   Frameworks/JSON Framework/dmg.sh
  L    Frameworks/JSON Framework/Tests
  L    Frameworks/JSON Framework/Site
  L    Frameworks/JSON Framework/JSON.xcodeproj
  L    Frameworks/JSON Framework/Docs
A  +   Frameworks/JSON Framework/bench.m
M      Frameworks/JSON Framework/JSON-Info.plist
M      Frameworks/JSON Framework/CREDITS
___
find . -name lock -print0 | xargs -0 rm
___
svn ci 
svn: Working copy '/Volumes/Home-etc/Users/prh/Projects/@otherpeoplesprojects/adium/branches/summer_of_code_2008' locked
svn: run 'svn cleanup' to remove locks (type 'svn help cleanup' for details)
___
rm ../.svn/lock 
___
svn ci
Sending        unit_testing
[snip]
svn: Commit failed (details follow):
svn: Directory '/branches/summer_of_code_2008/unit_testing' is out of date
___
svn up ..
svn: Failed to add file '../unit_testing/Frameworks/JSON Framework/dmg.sh': a file of the same name is already scheduled for addition with history
___
rm Frameworks/JSON\ Framework/dmg.sh 
___
svn up
D    Frameworks/libgmodule.framework/Versions/2.0.0/Resources/English.lproj
G    Frameworks/JSON Framework/Tests/Types.m
svn: Failed to add directory 'Frameworks/JSON Framework/Tests/json.org': a versioned directory of the same name already exists
___
svn st Frameworks/JSON\ Framework/Tests/json.org
A L+   Frameworks/JSON Framework/Tests/json.org
A  +   Frameworks/JSON Framework/Tests/json.org/1.json
A  +   Frameworks/JSON Framework/Tests/json.org/2.json
A  +   Frameworks/JSON Framework/Tests/json.org/1.plist
⋮
___
find . -name lock -print0 | xargs -0 rm
___
svn st Frameworks/JSON\ Framework/Tests/json.org
A  +   Frameworks/JSON Framework/Tests/json.org
⋮
___
cd ..
___
mv unit_testing unit_testing-fail
___
svn up unit_testing
A    unit_testing

With this fresh working copy, I’m able to build, so I believe I’m finally done.

New tool: sednames

Friday, June 20th, 2008

What if you could use sed to rename files?

Well, now you can.

sednames is a utility that lets you specify a program for sed on the command-line, which it then uses to rename the files that you also specify on the command-line.

The twist is that, unlike other batch-renamers, sednames also supports your VCS. For the most common cases (svn, hg, bzr, and git), there’s a –vcs option:

% sednames -e 's/Replace/Mix/' --vcs=hg *

This command-line will use Mercurial (hg mv) to rename every file by replacing “Replace” with “Mix”. Of course, if a name doesn’t contain “Replace”, then that name will be unchanged and sednames is smart enough to not try to rename that file.

Not only that, but just in case you’re using some oddball VCS (or you want to copy instead of rename, or something), sednames supports using any program to rename your files, as long as it accepts both the before and after names in its arguments. The –help output is more specific, but to put it simply, it works similarly to find‘s -exec option.

You can download the current revision directly, or use the Mercurial repository to follow or contribute to its development.

New tool: Localization Helper

Monday, June 16th, 2008

One thing that I noticed a few days ago while working on Growl 1.1.4 is that some strings aren’t translated in a couple of the localizations. I reported this on our localization mailing list, but it got me thinking: I could really use a program that would scan a tree of source code and tell me of problems like this.

So I wrote one.

Localization Helper is a command-line tool to walk a tree of source code looking for .strings files, and compare different localizations of them. It reports duplicate (not-translated) strings, and will soon (maybe by the time you read this) report strings that are missing altogether.

Currently, it only compares all other languages to one primary language, which defaults to English. I didn’t feel like making it compare every language to every other language. ☺

The program scans every directory you specify on the command-line. If you don’t give it any arguments, it scans the current working directory. Also, it has some options, which you can see with the –help option.

Here are some excerpts of its output for the Growl 1.1.3 source code:

*** Found problems in Core/Resources/*.lproj/Localizable.strings
Duplicate strings in Localizable.strings between English.lproj and cs.lproj:
"User went idle" = "User went idle";
"You are now considered idle by Growl" = "You are now considered idle by Growl";
"No activity for more than %d seconds." = "No activity for more than %d seconds.";
"Growl was unable to create the socket for Network notifications." = "Growl was unable to create the socket for Network notifications.";
"You are no longer considered idle by Growl" = "You are no longer considered idle by Growl";
⋮
!!! Warning: Localized file Extras/GrowlSafari/de.lproj/InfoPlist.strings is missing
!!! Warning: Localized file Extras/GrowlSafari/ja.lproj/InfoPlist.strings is missing

*** Could not read plist file at path Extras/GrowlSafari/de.lproj/Localizable.strings
*** Could not read plist file at path Extras/GrowlSafari/ja.lproj/Localizable.strings
*** Could not read plist file at path Extras/GrowlSafari/pt_BR.lproj/Localizable.strings
*** Could not read plist file at path Extras/GrowlSafari/sv.lproj/Localizable.strings

You’ll need to have either Leopard or Python 2.5 + PyObjC (Leopard comes with both) to use the program. (BTW: PyObjC rocks.)

Until I release it sometime later this week (hopefully), I provide two ways to get the program:

  1. You can download it directly.
  2. You can clone my repository:
    hg clone http://boredzo.org/localization-helper/hg/

Of course, if you clone your repository, then you can easily get updates by running hg pull.

Also, there’s a commits feed, in case you want to stay on top of localization_helper’s development using your feed reader.

UTI Property List Helper 1.0

Friday, March 21st, 2008

For a forthcoming blog post, I was recently writing a test app that displays images. This had me once again slogging through the list of System-Declared Uniform Type Identifiers and assembling type declarations for the image types by hand.

I’m tired of doing that. So I wrote a new application called UTI Property List Helper.

The main window consists of a table view, in which you enter UTIs, and two text views displaying chunks of Info.plist XML data.

This application automatically updates the CFBundleDocumentTypes and UTImportedTypeDeclarations arrays in its window with information on the types you enter, obtained from Launch Services’ database of all types registered by any app on your system.

No more searching for that OSType, or leaving it out because it’s too much work—this app does all the grunt work for you. Enjoy.

.hgignore for Mac OS X applications

Thursday, March 20th, 2008

If you use version control (and you should), then you’re familiar with the pollution that an Xcode build folder can put into your status output:

? build/.DS_Store
? build/Debug/UTI Plist Helper.app/Contents/Info.plist
? build/Debug/UTI Plist Helper.app/Contents/MacOS/UTI Plist Helper
? build/Debug/UTI Plist Helper.app/Contents/PkgInfo
? build/Debug/UTI Plist Helper.app/Contents/Resources/English.lproj/InfoPlist.strings
? build/Debug/UTI Plist Helper.app/Contents/Resources/English.lproj/MainMenu.nib/classes.nib
? build/Debug/UTI Plist Helper.app/Contents/Resources/English.lproj/MainMenu.nib/info.nib
? build/Debug/UTI Plist Helper.app/Contents/Resources/English.lproj/MainMenu.nib/keyedobjects.nib
⋮

Good version-control systems offer a way to ignore any file that matches a certain pattern. In the case of an Xcode project, you want to ignore the build folder and a few other things: .DS_Store files, backup nibs (those Foo~.nib packages that IB creates when you save), etc.

In Mercurial, the way to do that is to create a .hgignore file, and populate it with the patterns you want hg to ignore.

In order to save you repetitive work, here’s a .hgignore file, already fully-populated, that you can use when versioning your Xcode-based project with Mercurial:

File: hgignore.bz2

syntax: glob
build
*.swp

*.mode1
*.mode1v3
*.mode2
*.mode2v3
*.pbxuser
*.perspective
*.perspectivev3
xcuserdata

*~.nib

.DS_Store

What to do with this file

  1. Download it, and save the .bz2 file somewhere such as your Documents folder.
  2. cd into the top level of a repository.
  3. Extract the file using this command line: bunzip2 < ~/Documents/hgignore.bz2 > .hgignore
  4. Add the file: hg add .hgignore
  5. Commit it.

Thereafter, not only do you have a .hgignore file keeping your status output clean, but it’s versioned, so it’s easy for you to track and revert changes to the ignore file over time.

UPDATE 2011-05-05: Updated for Xcode 4.

A public thank-you

Monday, March 17th, 2008

This goes out to whichever engineers at Apple fixed the Image Unit template to not suck.

Before Xcode 2.5, that template was useless. Now, it contains everything I need already set up, to the maximum extent possible.

Thank you, Apple engineers.

hg st says modified, but hg diff doesn’t say anything

Tuesday, March 11th, 2008

You have a puzzle. When you run hg st, it says one of your files is modified:

hg st                                                              %~/Python/run_tests(0)
M test.py

But when you run hg diff, it doesn’t say anything about the file:

hg diff test.py                                                    %~/Python/run_tests(0)
___
                                                                   %~/Python/run_tests(0)

The reason, at least in my case, was that the file’s permissions had changed. hg st acknowledges this change exactly the same way it acknowledges a change to the file’s contents, but hg diff only shows changes to the contents of the file, not the metadata. Thus, a metadata change only gets reported by hg st and not by hg diff.

While I prefer hg over svn, this is one advantage that svn has over hg. hg only uses one column to indicate the type of change, and shows the same letter (M) for metadata changes as for content changes. svn, on the other hand, uses seven columns, and a metadata change puts the M in a different column.

There’s no way to make hg st use the svn st format, but you can make hg diff show metadata changes. The way to do this is to edit your hgrc file and enable git mode:

[diff]
git=True

There are two hgrc files; you can choose to change either or both. You can edit ~/.hgrc (this is what I recommend), or you can edit the per-repository .hg/hgrc file. (There is no .hg/hgrc file by default, so if you haven’t created one already, you would need to create it.)

The difference, as you’ve probably guessed, is that ~/.hgrc sets hg’s default for all repositories, whereas the per-repository hgrc changes the setting only for one repository, overriding ~/.hgrc and hg’s own defaults.

Once you make this change, the output from hg diff will include metadata information:

hg diff test.py                                                    %~/Python/run_tests(0)
diff --git a/test.py b/test.py
old mode 100755
new mode 100644
___
chmod a+x test.py                                                  %~/Python/run_tests(0)

Multi-stroke key bindings, Extended vi Edition

Thursday, February 21st, 2008

As some of you know, I use the multi-stroke key bindings by Jacob Rus to easily type strings such as ⌃⇧⌘⌫. I mostly use these characters in three places:

  1. Correspondence with users
  2. Posts here
  3. Documentation

And it sure beats looking those characters up in UnicodeChecker or the Character Palette.

But I was never satisfied with the original set of key bindings that that file provides. The first problem that I had was that the arrow keys are emacs keys, not vi keys:

Solid Left (back) ⌃M + ⌃B
Solid Right (forward) ⌃M + ⌃F
Solid Up (previous) ⌃M + ⌃P
Solid Down (next) ⌃M + ⌃N
Dotted Left (back) ⌃M + B
Dotted Right (forward) ⌃M + F
Dotted Up (previous) ⌃M + P
Dotted Down (next) ⌃M + N

I’m a vi guy, so I wanted the hjkl arrangement for my arrows. So that was the first change I made.

I also added a bunch of characters, and reassigned some of them:

Additions:
Ch Description Key sequence
Return ^M + Return
Command ^M + C
Command (Apple) ^M + ^A
Option ^M + ^O
Control ^M + ^C
Shift ^M + ^S
Caps Lock ^M + ^S
Smiling face ^M + ^F
Frowning face ^M + F
× Multiplication sign ^M + ^X
Modifications:
Ch Description Key sequence Reason for modification
The arrows (solid and dotted), as noted above Because I prefer vi to emacs
Return ^M + ^R To free up ^E for Escape
Enter ^M + R To free up E for Eject
Home ^M + Shift-^H To free up ^H for ←
End ^M + Shift-^E To free up H for ⇠
Escape ^M + ^E To free up ^X for × (multiplication sign)
Eject ^M + E To keep Eject on the same key as Escape

In case you’re wondering about the division sign (÷): The OS already provides this as ⌥/, and always has been (all the way back to 1984). I didn’t need to add it.

Also, in case you’re wondering about “Apple” above: The Apple logo is ⇧⌥K, so I didn’t need to add that, either. (Plus, I never use it.)

File: DefaultKeyBinding.dict.bz2

This file contains all of the above bindings, as well as the ones from Jacob Rus’ original that I didn’t change.

To install it, put it in the KeyBindings folder in the Library folder in your Home folder. You may need to create the folder. The bindings are loaded on application launch by Cocoa, so you will need to relaunch any already-running applications in order to use the bindings in them.

Quickie: Finding an svn conflict marker with vim

Tuesday, February 12th, 2008

/[<=>]\{7\}

Weirdest compiler warning I’ve ever seen

Saturday, February 9th, 2008

Here’s the code:

[[GrowlApplicationController sharedController] setQuitAfterOpen:YES];

And here’s the warning:

Core/Source/GrowlApplicationBridgePathway.m:43: warning: ‘GrowlPreferencesController’ may not respond to ‘-setQuitAfterOpen:’

Wait, what? I said GrowlApplicationController, not GrowlPreferencesController!

The problem is that the compiler is recognizing “sharedController” as a class method, and assuming that I’m using the class that it knows has it—which, in this case, is GrowlPreferencesController. GrowlPreferencesController is declared in its header, which is imported by the prefix header; GrowlApplicationController, meanwhile, is declared only with @class (in another header). So I think it’s assuming that GrowlApplicationController is a subclass or something.

However, +[GrowlPreferencesController sharedController] is typed as returning a GrowlPreferencesController *, so when I try to call a GrowlApplicationController method on it, it gives me that warning.

The fix is to #import "GrowlApplicationController.h" so that the compiler knows that GrowlApplicationController has a sharedController method of its own.

Generating a tarball of your project

Friday, February 8th, 2008

Every good version-control system has this as a built-in feature.

In the below, $TMP is a staging directory, such as “build”, “/tmp”, or “/Volumes/RAM Disk”. It’s also where the final archive will end up.

  • svn: Sort of. svn export $TMP/MyProject && cd $TMP && tar cjf MyProject.tbz MyProject
  • darcs: darcs dist (outputs a .tar.gz file in the current working directory)
  • bzr: bzr export --format=tbz2 $TMP/MyProject.tbz
  • git: git archive --format=tar master | bzip2 > $TMP/MyProject.tbz
  • hg: hg archive -p MyProject -t tbz2 $TMP/MyProject.tbz

More Darcs win: Selective recording

Wednesday, December 12th, 2007

Darcs lets you choose which changes you want to include in a patch (commit). For example:

darcs rec                                                   %~/ASL test apps/aslsearch(0)
move ./aslsearch.m ./aslsearch.c
Shall I record this change? (1/?)  [ynWsfqadjkc], or ? for help: y
hunk ./aslsearch.c 1
-#import <Foundation/Foundation.h>
-
-#include <stdlib.h>
-#include <stdbool.h>
-#include <stdio.h>
-#include <unistd.h>
-#include <string.h>
-#include <vis.h>
-#include <asl.h>
-
Shall I record this change? (2/?)  [ynWsfqadjkc], or ? for help: n
hunk ./aslsearch.c 29
-       NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
-
Shall I record this change? (3/?)  [ynWsfqadjkc], or ? for help: y
hunk ./aslsearch.c 75
-       [pool release];
Shall I record this change? (4/?)  [ynWsfqadjkc], or ? for help: y
hunk ./aslsearch.c 194
+       fprintf(stderr, "key: \e2\80\9c%s\e2\80\9d\noperator: 0x%x\nvalue: \e2\80\9c%s\e2\80\9d\n", key, operator, value);
Shall I record this change? (5/?)  [ynWsfqadjkc], or ? for help: n
hunk ./aslsearch.xcodeproj/project.pbxproj 10
-               8DD76F9A0486AA7600D96B5E /* aslsearch.m in Sources */ = {isa = PBXBuildFile; fileRef = 08FB7796FE84155DC02AAC07 /* aslsearch.m */; settings = {ATTRIBUTES = (); }; };
+               8DD76F9A0486AA7600D96B5E /* aslsearch.c in Sources */ = {isa = PBXBuildFile; fileRef = 08FB7796FE84155DC02AAC07 /* aslsearch.c */; settings = {ATTRIBUTES = (); }; };
Shall I record this change? (6/?)  [ynWsfqadjkc], or ? for help: ?
How to use record...
y: record this patch
n: don't record it
w: wait and decide later, defaulting to no

s: don't record the rest of the changes to this file
f: record the rest of the changes to this file

d: record selected patches, skipping all the remaining patches
a: record all the remaining patches
q: cancel record

j: skip to next patch
k: back up to previous patch
c: calculate number of patches
h or ?: show this help

<Space>: accept the current default (which is capitalized)
hunk ./aslsearch.xcodeproj/project.pbxproj 10
-               8DD76F9A0486AA7600D96B5E /* aslsearch.m in Sources */ = {isa = PBXBuildFile; fileRef = 08FB7796FE84155DC02AAC07 /* aslsearch.m */; settings = {ATTRIBUTES = (); }; };
+               8DD76F9A0486AA7600D96B5E /* aslsearch.c in Sources */ = {isa = PBXBuildFile; fileRef = 08FB7796FE84155DC02AAC07 /* aslsearch.c */; settings = {ATTRIBUTES = (); }; };
Shall I record this change? (6/?)  [ynWsfqadjkc], or ? for help: f
hunk ./aslsearch_Prefix.pch 5
-#ifdef __OBJC__
-#      import <Foundation/Foundation.h>
-#endif
-
Shall I record this change? (10/?)  [ynWsfqadjkc], or ? for help: y
hunk ./aslsearch_Prefix.pch 8
+#include <unistd.h>
Shall I record this change? (11/?)  [ynWsfqadjkc], or ? for help: n
What is the patch name? Switched source file from Obj-C to pure C
Do you want to add a long comment? [yn]n
Finished recording patch 'Switched source file from Obj-C to pure C'

(Before you mention the UTF-8 sequences that Darcs called out as invalid ASCII: Yes, I know.)

From svn to darcs: Listing unknown files

Sunday, December 2nd, 2007

If you’re an svn user looking at darcs, you may wonder how to find unknown files—that is, files that you’ve created in your working directory, but haven’t yet told your VCS about (with svn add or darcs add). In svn, the command is svn st, but in darcs, the procedure is not obvious.

The darcs command that most seems like what you need is darcs whatsnew, or, as I type it, simply darcs what. Like svn st, this shows you all files that have been added, removed, or modified; it also includes a diff, so not only is it your substitute for svn st, it’s also your substitute for svn diff. But it doesn’t show you unknown files.

Until you use the -l (that’s a lowercase L) option.

darcs what -l changes the output greatly, as it’s in a completely different format: one similar to that of svn st. The format is actually that of darcs what -s (summary mode); the difference is that -l also lists unknown files.

There are some differences between darcs what -l and svn st. Some of these differences are:

  • darcs uses fewer columns than svn: It has one status column, whereas svn has six.
  • darcs uses ‘a’ to indicate an unknown file, whereas svn uses ‘?’.
  • darcs uses ‘R’ to indicate a file that you’ve removed, whereas svn uses ‘D’ to indicate a file that you’ve deleted. (svn supports remove and rm as synonyms for its delete command, but darcs does not support delete nor rm as synonyms for its remove command.)

darcs is becoming my favorite VCS

Friday, October 26th, 2007

Here’s a real-world situation that tells you why I’m falling in love with darcs.

I’m working on a plug-in that logs all notifications posted by Apple Mail to a couple of files. The idea of this plug-in is to enable Mail-bundle developers to develop more reliable plug-ins by listening for notifications rather than posing as Mail’s classes or swizzling its methods.

(As you know, I don’t ordinarily announce what I’m working on before it’s ready; for this post, it’s important to provide context.)

The first revision of the plug-in used NSLog, but I found that this spammed console.log rather heavily. So I changed it to use two separate log files, which I write to using NSFileHandles.

Then I started work on a new feature: datestamps. While working on that, however, I noticed that I had forgotten to close and release my two file handles in -dealloc. Oops.

Now, remember that I had local changes. If this code were versioned in svn, I would have to:

  1. bzip2 the source file (which renames it as a side-effect).
  2. svn up the file in order to restore the pristine copy.
  3. Open it in a separate editor (so as not to lose undo history) and add the closeFile and release messages that I’d forgotten.
  4. Save and exit.
  5. Commit.
  6. Delete the committed file and bunzip2 my original one.
  7. Go through an svn diff and redo the fix in my modified file so I don’t clobber the changes in my next commit.

What a hassle. But darcs has first-class support for just this situation.

All I had to do was darcs amend-record. darcs asked me whether the most recent commit was the one I wanted to amend, then it offered me the choice of which hunks of local changes I wanted to include in the amendment. I said yes to the one with the closeFile and release messages and no to all the others. That’s all I had to do.

Let me summarize that for you: I was able to retroactively fix my previous commit while I had other local changes in the same file, without sweeping up those other changes into the commit.

That’s awesome.

What do different VCSs do when a file changes?

Thursday, October 11th, 2007

Here’s the scenario:

  1. You change a file.
  2. You invoke your-vcs-here commit file.txt (or equivalent).
  3. You write a commit message in the editor.
  4. You suspend the editor with ^z and make another change to a file. (Perhaps you started the commit and a build at the same time, and the build failed because you forgot a comma.)
  5. You resume the editor, and issue :wq or ^x^c to save and exit.

What happens?

Subversion

svn commits the changed version of the file—in other words, the file as it existed when the editor exited.

Monotone

mtn notices that the file changed, tells you “you can’t do that”, and throws away saves the commit message.

More precisely, it says:

mtn: misuse: file 'Test.txt' modified during commit, aborting

darcs

darcs commits the file as it existed before it started the editor. (Presumably, it copies changed files to a staging area rather than simply committing them from the WC.)

Mercurial

hg commits the file as it existed before it started the editor, just as darcs does.


UPDATE 2008-03-05: Jack, in a comment below, corrected me on what Monotone does with the commit message. I have applied this correction above.

UPDATE 2008-03-26: jpc, in a comment below, corrected me on what Mercurial does with the commit message. I thought that that’s what I had written, but I wrote the wrong thing: that it acted like svn, rather than darcs. I apologize for the brainfart, and I have applied this correction above.

Growl Registration Dictionary Editor

Monday, October 1st, 2007

Since 0.7, Growl has been able to detect a registration dictionary in your application when your app launches—all you have to do is put it in a file with a certain name inside your app bundle. We call this feature auto-discovery or auto-registration.

I don’t think people have been taking much advantage of this feature, though, for two reasons:

  1. We didn’t mention it in the docs. (This would be the major reason.)
  2. There was no handy-dandy editor to quickly bash out a registration dictionary file.

Sure, we can all use Property List Editor, but that’s not a tool honed to the purpose. You have to rifle through GrowlDefines.h to find all the different keys you can use in the dictionary (the docs only cover the most basic set).

Well, now there’s a tool honed to the purpose.

Here’s beta 1 of the Growl Registration Dictionary Editor. I invite you to try it. Obvious things worth doing:

  • Creating new reg dicts
  • Editing existing ones
  • Importing a reg dict from your saved tickets (great for porting your existing app to use auto-reg!)

Feedback will be appreciated. If you’re subscribed to the Growl discussion list, you can send it there; if not, comments here are OK.

The source for the GRDE is in the Growl source repository.

If this announcement looks familiar to you, don’t worry; you’re not going crazy. I posted a similar message to the Growl discussion list, then decided to edit it a bit and post it here, since I know that at least several of you are Growl framework users who can put this app to good use.