Setting the Timezone in a Docker image

This is a micro-tip. If you have a Debian/Ubuntu container that is set to the wrong timezone and doesn’t have the normal tools for setting that timezone, here’s how to do it. Add the following lines to your Dockerfile:

# Set the timezone.
RUN sudo echo "America/New_York" > /etc/timezone
RUN sudo dpkg-reconfigure -f noninteractive tzdata

This particular example sets the timezone to Eastern Standard Time. Here’s the full list of locales.

Mocking Logger in Grails Spock Unit Tests

When unit testing services in Grails and Spock you can run into issues when your service logs messages. The problem is that Spock doesn’t automatically add the log property to your service. You’ll find most of the answer here but it’s not complete and doesn’t work as is. Most of the time you also don’t need the actual log messages, you’re testing the code. So, here’s the simple solution. Add this method to your Spock test class:

private def mockLog(def service) {
    def impl = [
            debug:{String message ->
                println "debug: ${message}"
            info:{String message ->
                println "info:  ${message}"
            warn:{String message ->
                println "warn:  ${message}"
            error:{String message ->
                println "error: ${message}"

    def logInstance = impl as org.apache.commons.logging.Log
    service.metaClass.log = logInstance

Now, in your test setup, just call the method mockLog(myService). If you don’t want the log messages littering your output, then replace the println with no-op calls.

Read More

Using GVM with the Fish Shell

The Fish Shell is awesome but it doesn’t have a Posix-compatible shell language like Bash or ZSH. This is a problem when you’re trying to use third-party tools that expect a Posix-compatible shell. Tools like GVM, the Groovy enVironment Manager.

GVM, like it’s inspiration RVM, lets a developer switch between versions of the language toolchain. This is important because modern languages are moving targets and building an application against one version of the toolchain isn’t guaranteed to run against later versions.

GVM, in particular, wires itself into the Bash and ZSH shells. The best approach to getting GVM to work with Fish is to rewrite it for Fish. But the developers for GVM don’t seem inclined to do that. So we have to cheat.

Read More

Brew Cask Check Versions

Homebrew Cask doesn’t yet check for new versions. Below is a hacked together script that does just that. This is very rough and is only intended as a stop-gap until the Cask developers complete the real functionality.

Updated Sep 19, 2014: Found and fixed some errors and added command-line parameters (-v for verbose checking and you can pass a cask name to check one specific cask).

#!/usr/bin/env ruby
require 'nokogiri'
require 'open-uri'
require 'optparse'

CASK_DIR = '/usr/local/Library/Taps/caskroom/homebrew-cask/Casks'

# Force one version to be treated as another. Used when the local version and appcast version don't match up.
	'alfred' => [['2.4_279', '2.4']]

def extract_version(content)
	content.split(/\n/).find {|line| line.strip =~ /^[0-9].*/}

options = {} do |opts|
  opts.banner = "Usage: check_versions.rb [options] [cask-name]"

  opts.on("-v", "--verbose", "Run verbosely") do |v|
    options[:verbose] = v

target_cask_name = ARGV[0] if ARGV.size > 0

# if a cask name was given on the command line, just check that one cask
if target_cask_name.nil?
	cask_list = `brew cask list -1`.split(/\n/)
	cask_list = [target_cask_name]

cask_list.each do |cask_name|
	puts "Checking app #{cask_name}" if options[:verbose]

	# get the cask version and app location from the cask formula
	results = `brew cask info #{cask_name}`.split(/\n/)
	cask_version = results[0].split(':')[1].strip
	puts "    Cask version #{cask_version}" if options[:verbose]
	app_line = results.find {|line| line =~ /\.app \(/ }

	if app_line
    	cask_location = "#{CASK_DIR}/#{cask_name}.rb"
		app_location = app_line.gsub(/\(link\)/, '').split(/\//)[-1].strip

		# check for the installed app version in two places, prefer the short version string to the long
		short_local_version = extract_version(`defaults read "/Applications/#{app_location}/Contents/Info.plist" CFBundleShortVersionString 2>/dev/null`)
		long_local_version = extract_version(`defaults read "/Applications/#{app_location}/Contents/Info.plist" CFBundleVersion 2>/dev/null`)
		local_version = short_local_version || long_local_version

		puts "    Installed app version #{local_version}" if options[:verbose]

		if File.exists?(cask_location)
			# since the appcast stanza doesn't show up in 'brew cask info', parse it from the formula file
			appcast_line = {|line| line =~ /[\w]*appcast .*/}
			if appcast_line
				appcast_url = appcast_line.split("'")[1]
				puts "    Checking for sparkle info at #{appcast_url}" if options[:verbose]
				# download the sparkle xml and parse it
				doc = Nokogiri::HTML(open(appcast_url))
				# attempt to locate the newest version in the xml
				cur_version = '0'
				appcast_version = nil
				doc.xpath('//rss/channel/item/enclosure').each do |ele|
					# spark:version _should_ be comparable
					item_version = ele['sparkle:version']
						if >
							cur_version = item_version
							appcast_version = ele['sparkle:shortversionstring'] || item_version
						# if the spark:version isn't comparable, just assume this item is newer then the previous
						cur_version = item_version
				puts "    Appcast version #{appcast_version}" if options[:verbose]
				puts "    No appcast entry for app" if options[:verbose]

		# the latest version is the sparkle version if present, otherwise it's the cask formula version
		latest_version = appcast_version || cask_version

		# check for a version override in the VERSION_MAPPING entries
		mapping = VERSION_MAPPING[cask_name]
		compare_version = latest_version
		if mapping
			match_version = mapping.find {|entry| entry[0] == latest_version}
			if match_version
				compare_version = match_version[1]

		# it all comes down to this, try to determine if there's a newer version. if the version numbers can't be
		# parsed, just do a straight comparison.
			if (latest_version != 'latest') && ( >
				puts "#{cask_name} : latest version #{latest_version}, local version #{local_version}"
			if (latest_version != 'latest') && (compare_version.strip != local_version.strip)
				puts "#{cask_name} : latest version #{latest_version}, local version #{local_version}"

Sublime Text 2 Command-line MicroTip

I’ve struggled with a long-standing bug that causes the Sublime Text 2 command-line launcher to fail sporadically. In order to use the command-line launcher, the documentation directs you to create a symbolic link to the command-line utility stored in the application bundle:

ln -s "/Applications/Sublime Text" ~/bin/subl

Then, assuming you have a bin directory in your home directory, and it’s in your path, you can open files in ST2 from the command line:

subl file_i_want_to_edit.txt

The problem is that the subl command is buggy. If ST2 isn’t running, or if it is running and there’s an open window, everything works well. The file is opened and it appears in a new tab. But, if ST2 is running and doesn’t have any open windows, the subl command will silently fail. ST2 will open a blank window with no tabs and no content.

There’s a simple hack that gives you 90% of what you want from the subl command. You can create an alternate command that works 100% of the time to open files. Create a one-line shell script that looks like this:

open -a "/Applications/Sublime Text" $@

Save it wherever you like and use that script to open files. This isn’t a replacement for the subl command. That command supports command-line arguments like ‘-w’ to allow ST2 to be used as an editor for Git and other tools. This script is purely for opening files from the command-line.

Read More

Alfred 2 Workflows

Alfred 2 will be coming out soon. It’s big new feature is the ability to easily create “workflows”, plug-ins that extend Alfred’s capabilities.

I’ve been having a lot of fun creating new workflows and extending the work of others. The workflows are in my GitHub account for anyone interested in forking or examining them.

Read More