Scriptd

A collection of scripts that I have written or found to be useful.

I am now posting over at my new website. Hopefully I’ll update a bit more often than I did over here.

I use the Skype 5 client pretty often while I’m at work. It pops up a Growl notification whenever a new message comes in, but if I’m not looking at the screen (or if I’m looking at another monitor), I’ll oftentimes miss it. The dock icon shows a badge, but my dock is set to auto-hide so I can’t see the badge until I move the mouse to the dock.

As a result of all this, I wind up missing Skype messages all of the time. What I’d really like is some sort of indicator on the menu bar to tell me when I have unread messages. Unfortunately, Skype doesn’t provide this option (one of a long list of shortcomings of the Skype UI). So, I wanted to try to hack something together to fill this need.

Dock icon badges are presented in OS X via the NSDockTile class. There is a badgeLabel property that contains the text that gets shown in the little red badge bubble. Unfortunately, I couldn’t figure out a way to access this information from an external script.

I had heard of F-Script a while ago as a tool for debugging Cocoa applications. I won’t go into details about all you can do with F-Script (and my knowledge of the tool is pretty limited). I had played around with it enough to get a feel for some of the things it could do and figured it might be of use for this issue.

One of the cool features of F-Script is the ability to inject itself into a running Cocoa application. It’s got the ability to add a console to a running app, allowing you to access objects in the app. You set it up using GDB:

$ gdb
(gdb) attach Skype
(gdb) p (char)[[NSBundle bundleWithPath:@"/Library/Frameworks/FScript.framework"] load]
(gdb) p (void)[FScriptMenuItem insertInMainMenu]
(gdb) detach
(gdb) quit

Once you do that, you get a menu item in the target application (here, Skype). You can then open a console and play around:

> app := NSApplication sharedApplication

> app dockTile badgeLabel
nil

This is what appears if there is no badge icon. Do this again when there is a badge (in this case, I have a “1” badge) and you see the following:

> app dockTile badgeLabel
'1'

Great, so I figured out a way to access the badge from a script. But the process is a bit clunky and certainly not something I’d want to do on a daily basis. It’s a step in in the right direction, though.

F-Script also allows you to create your own Objective-C objects that interact with the running application. After a bit of trial and error, I came up with the following NSObject-derived class that accomplished what I wanted to do.

SkypeMenuUpdater : NSObject
{
    statusBarItem
    timer

    - init
    {
        self := super init.
        self ~~ nil ifTrue:
        [
            statusBarItem := NSStatusBar systemStatusBar statusItemWithLength:20.

            timer := NSTimer scheduledTimerWithTimeInterval:1
                                                     target:[self updateMenu]
                                                   selector:#value
                                                   userInfo:nil
                                                    repeats:YES.
        ].
        ^ self
    }

    - updateMenu
    {
        statusBarItem setTitle:NSApplication sharedApplication dockTile badgeLabel.
    }
}.

updater := (SkypeMenuUpdater alloc) init.

Here, I define a new class called SkypeMenuUpdater with two ivars. The constructor creates a NSStatusBarItem called statusBarItem and a NSTimer called timer set to fire once a second. When the timer fires, it calls the updateMenu method, which queries the badge label of the app’s dock tile, and then sets the status bar item to reflect the value of the label. Finally, it instantiates the object, which kicks the whole thing off.

The last piece involves injecting this script into a running Skype instance. I chose Ruby, since someone had a nice gist on Github showing how to do something similar. The final script looks something like this:

#!/usr/bin/env ruby

FSCRIPT_PATH = "/Library/Frameworks/FScript.framework"

GDB = IO.popen("gdb", "w")
def gdb(cmd)
    GDB.puts cmd
    GDB.flush
end

updaterScript = <<END_OF_FSCRIPT
SkypeMenuUpdater : NSObject
{
    statusBarItem
    timer

    - init
    {
        self := super init.
        self ~~ nil ifTrue:
        [
            statusBarItem := NSStatusBar systemStatusBar statusItemWithLength:20.

            timer := NSTimer scheduledTimerWithTimeInterval:1
                                                     target:[self updateMenu]
                                                   selector:#value
                                                   userInfo:nil
                                                    repeats:YES.
        ].
        ^ self
    }

    - updateMenu
    {
        statusBarItem setTitle:NSApplication sharedApplication dockTile badgeLabel.
    }
}.

updater := (SkypeMenuUpdater alloc) init.
END_OF_FSCRIPT

updaterScript.gsub!("\n", "\\n")

gdb "attach Skype"
gdb "p (char)[[NSBundle bundleWithPath:@\"#{FSCRIPT_PATH}\"] load]"
gdb "p (void)[[FSInterpreter interpreter] execute:@\"#{updaterScript}\"]"
gdb "detach"
gdb "quit"

GDB.close

This is available on Github. All you have to do is run the Ruby script and it’ll do the rest of the work for you.

It requires F-Script to be installed (the script assumes it’s installed in /Library/Frameworks/FScript.framework). It also requires GDB, which is installed as part of the Xcode command line tools. Also note, you’ll have to run the script each time you launch Skype.

Feel free to try this out. Use it at your own risk, though. I make no guarantees that it will work perfectly under all configurations. Also, I feel that this kind of hack is generally a bad idea. It gets the job done for me, but I’d love to have a less hacky way of accomplishing this.

Feel free to send along any questions or comments.

I saw an article on Twitter about converting Markdown to Microsoft Word docx. The author gives a workflow that involves first converting to an OpenOffice format (FODT) then opening the FODT document in OpenOffice to convert to a docx.

Turns out, there is a easier way to do the conversion from Markdown to docx using Fletcher Penny’s Multimarkdown. This assumes that mmd is installed in /usr/local/bin. Adjust the workflow accordingly if you have it installed elsewhere.

  1. Open Automator.
  2. Create a new Service.
  3. Change the settings at the top to read: “Service receives selected files or folders in any application.”
  4. Drag “Run Shell Script” into the workflow.
  5. Under “Run Shell Script” set “Pass input” to “as arguments.”
  6. Enter the following text for the shell script:

    for f in "$@"
    do
        cat "$f" | /usr/local/bin/mmd | textutil -stdin -convert docx -format html -output "$f.docx"
    done
    

This will perform the conversion, leaving a docx file in the same directory as the original Markdown file.

I’ve been on a Python kick lately (more on that in another post), so I decided to re-write my did script in Python. This wasn’t a purely academic exercise to practice my Python. Rather, my old did script was missing a key feature: wildcards. This became abundantly clear when I tried to use it to diff a couple hundred files recently.

To recap, this script is useful when you want to compare a set of files in two directories, laid out as follows:

current_dir/
|
+--subdir1/
|  |
|  +--file1.txt
|  |
|  +--file2.txt
|
+--subdir2/
   |
   +--file1.txt
   |
   +--file2.txt

I want to be able to compare file1.txt in subdir1 with file1.txt in subdir2. Same for file2.txt. With my original script, I’d run the following:

did subdir1 subdir2 file1.txt file2.txt

The new Python version allows the use of wildcards:

did subdir1 subdir2 *.txt

The script makes use of Python’s globbing functionality (explained here if you’re interested). It expands the * wildcard in filenames (like in *.txt) as well as in directory names (like ../lib/*/*.m).

As in the original version, the actual diff command is specified by the DIFFCMD variable.

#!/usr/bin/env python

import glob
import os
import subprocess
import sys

DIFFCMD = "ksdiff"
USAGE = "Usage: did dir1 dir2 filename(s)"

def expand_globs(dir, globs):
    # expand all of the globs
    expanded = []
    for g in globs:
        expanded.extend(glob.glob(dir + os.sep + g))

    # then make them all relative to the specified path
    result = []
    for p in expanded:
        result.append(os.path.relpath(p, dir))

    return result

def run_diff(ldir, lpaths, rdir, rpaths):
    # find items in left that aren't in right
    left_not_right = [item for item in lpaths if item not in rpaths]
    if len(left_not_right) > 0:
        print "The following files are in %s but not in %s" % (ldir, rdir)
        for f in left_not_right:
            print "   %s" % f

    # find items in right that aren't in left
    right_not_left = [item for item in rpaths if item not in lpaths]
    if len(right_not_left) > 0:    
        print "The following files are in %s but not in %s" % (rdir, ldir)
        for f in right_not_left:
            print "   %s" % f

    # find items in both left and right
    files_to_compare = list(set(lpaths) & set(rpaths))
    files_to_compare.sort()

    # run the comparison
    for f in files_to_compare:
        lf = ldir + os.sep + f
        rf = rdir + os.sep + f
        print "Comparing %s" % f
        subprocess.call([DIFFCMD, lf, rf])

def main():
    if len(sys.argv) < 4:
        print USAGE
        sys.exit()

    ldir = sys.argv[1]
    rdir = sys.argv[2]
    args = sys.argv[3:]

    lpaths = expand_globs(ldir, args)
    rpaths = expand_globs(rdir, args)

    run_diff(ldir, lpaths, rdir, rpaths)

if __name__ == "__main__":
    main()

If the wildcards match a file that’s in one directory but not the other, those files are printed to stdout and not passed on to the diff command.

I do a fair amount of work in Markdown these days. I won’t get into the virtues of Markdown here. Chances are, if you have stumbled upon this page you’re already using Markdown and don’t need me to tell you why it’s a good idea.

I wanted a workflow that simplifies composing email in Markdown. Whether I am writing it in a text editor (generally BBEdit or Byword) or in my mail client itself, I wanted a quick way of converting Markdown to formatted text.

The main part of this workflow is a fairly simple shell script, which you’ll find below.

The script takes Markdown text on the pasteboard, converts it to rich text, and puts that rich text back on the pasteboard. This allows you to copy (or cut) a bunch of Markdown text, run the script, and paste it formatted into your email editor.

I have bound the script, using FastScripts, to ⌥⇧⌘D (Alt-Shift-Command-D).

This allows me to write a bunch of Markdown in Mail.app, select, cut, convert, and paste using the following three shortcuts: ⌘X (Cut) ⌥⇧⌘D (Convert) ⌘V (Paste)

Here’s the script, which I called Pasteboard Markdown to RTF:

#!/usr/bin/env bash

CSS_FILE="file:///Users/paul/Dropbox/Stylesheets/PasteboardMarkdown.css"

(echo "CSS: $CSS_FILE" ; echo ; pbpaste) | \
/usr/local/bin/mmd | \
textutil -stdin -stdout -convert rtf -format html | \
pbcopy

First, I define a variable called CSS_FILE that points to the CSS file I want to use to format the Markdown file. I took swiss.css from the Marked application bundle (found in /Applications/Marked.app/Contents/Resources/), copied it to my Dropbox folder, and renamed it to PasteboardMarkdown.css. You can substitute it with whatever CSS you prefer and place it wherever you like. Just make sure the CSS_FILE variable points to it using the proper file:/// URL syntax.

Next is a big Bash one-liner. The MultiMarkdown processor recognizes an input line that specifies the CSS file to be used. That’s what the first echo does. The second echo gives us a blank line (it doesn’t work otherwise). The pbpaste command pastes the contents of the pasteboard to stdout. The two echoes and the pbpaste combine the CSS specifier and the Markdown text from the clipboard.

All of that gets piped into the mmd MultiMarkdown processor, which takes Markdown text from stdin and writes HTML to stdout.

The HTML output is then piped into textutil which, using the options provided here, takes HTML from stdin and writes RTF to stdout.

Finally, the RTF output is piped into pbcopy which takes whatever is written to stdin (in this case, the RTF text) and puts it on the pasteboard.

Note, the script requires Fletcher Penny’s MultiMarkdown processor.

This one has saved me tons of time. It’s a Python script that pretty prints JSON data.

#!/usr/bin/python
import fileinput
import json
if __name__ == "__main__":
    jsonStr = ''
    for a_line in fileinput.input():
        jsonStr = jsonStr + ' ' + a_line.strip()    
    jsonObj = json.loads(jsonStr)  
    print json.dumps(jsonObj, sort_keys=True, indent=2) 

On its own, this is a useful script. As the author points out, you can also make this into a BBEdit Text Filter by placing the script in ~/Library/Application Support/BBEdit/Text Filters (or ~/Dropbox/Application Support/BBEdit/Text Filters if you’re using BBEdit’s Dropbox integration). This has come in handy for me when used in conjunction with bbcurl. I use bbcurl to download the JSON data directly to BBEdit and then use the “Pretty Print JSON” Text Filter to make it readable.

I recently needed to write some SQL queries to be run against a SQLite database. Naturally, I started writing the queries in BBEdit. However, actually executing the queries became tedious. I would either need to copy-and-paste the query to Base (the utility I use for viewing SQLite3 databases) or save the SQL query, open the Terminal, and run the following:

sqlite3 database_filename < sql_query_filename

I thought it would be nicer to do it all in BBEdit. So, I wrote this AppleScript. It executes the query and loads the results as a new BBEdit document.

tell application "BBEdit"
    set _contents to contents of document 1
    set _file to file of document 1
    set _sqlName to name of document 1
end tell

set _firstLine to paragraph 1 of _contents
if _firstLine starts with "-- " then
    set _dbName to characters 4 thru end of _firstLine as string
else
    display alert "No database specified" buttons {"OK"} ¬
        message "The file must begin with a SQL comment containing the SQLite database's filename."
    return
end if

set _sqlFilename to quoted form of POSIX path of _file
set _dir to do shell script "dirname " & _sqlFilename
set _dbFilename to _dir & "/" & _dbName

set _dbOutput to do shell script "sqlite3 -header -column \"" & _dbFilename & "\" < " & _sqlFilename

tell application "BBEdit"
    set _document to make new document
    set contents of _document to _dbOutput
    set name of _document to _sqlName & " Output"

    tell text of _document
        select insertion point before line 1
    end tell
end tell

The script makes two assumptions. First, that the database and the query file are in the same directory. Second, that the SQL query file begins with a comment containing the database filename. So, for example, the following query:

-- database_filename.sqlite
SELECT * FROM sometable;

will be run against database_filename.sqlite which must be saved in the same directory as the query. The results will be shown as a new BBEdit document.

I named the script “Run SQL Query” and bound it to Cmd-Shift-R in BBEdit.

Update: I rewrote this script in Python and added the ability to use wildcards. See this post for details.

Compares a list of files in two directories. I find this useful when merging changes between directories.

As an example, let’s say you have two directories laid out as follows:

current_dir/
|
+--subdir1/
|  |
|  +--file1.txt
|  |
|  +--file2.txt
|
+--subdir2/
   |
   +--file1.txt
   |
   +--file2.txt

I want to be able to compare file1.txt in subdir1 with file1.txt in subdir2. I also want to be able to compare file2.txt in subdir1 with file2.txt in subdir2. Using the script, I’d run the following:

did subdir1 subdir2 file1.txt file2.txt

Note, the DIFFCMD variable can be changed to use other diff programs. Here, I’m using BBEdit.

#!/usr/bin/env bash

# did : Diff In Directories
# Usage: did dir1 dir2 file1 file2 ... fileN
# Compares dir1/file1 with dir2/file1, dir1/file2 with dir2/file2, etc.

DIFFCMD=bbdiff

if [ $# -lt 3 ]
then
    echo "Usage: $0 directory1 directory2 file ..."
    exit
fi

dir1=$1
shift

dir2=$1
shift

for f in "$@"
do
    $DIFFCMD $dir1/$f $dir2/$f
done

An Applescript to open a Terminal window in the root directory of an Xcode project.

tell application "Xcode"
    set _workspace to active workspace document
    set _workspaceFile to file of _workspace
end tell

tell application "Finder"
    set _posixTargetPath to quoted form of POSIX path of _workspaceFile
end tell

tell application "Terminal"
    activate
    do script "cd " & _posixTargetPath & "/../.."
end tell