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.
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.
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.
VERSION_MAPPING = {
'alfred' => [['2.4_279', '2.4']]
}
def extract_version(content)
content.split(/\n/).find {|line| line.strip =~ /^[0-9].*/}
end
options = {}
OptionParser.new do |opts|
opts.banner = "Usage: check_versions.rb [options] [cask-name]"
opts.on("-v", "--verbose", "Run verbosely") do |v|
options[:verbose] = v
end
end.parse!
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/)
else
cask_list = [target_cask_name]
end
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 = File.open(cask_location).find {|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']
begin
if Gem::Version.new(item_version) > Gem::Version.new(cur_version)
cur_version = item_version
appcast_version = ele['sparkle:shortversionstring'] || item_version
end
rescue
# if the spark:version isn't comparable, just assume this item is newer then the previous
cur_version = item_version
end
end
puts " Appcast version #{appcast_version}" if options[:verbose]
else
puts " No appcast entry for app" if options[:verbose]
end
end
# 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]
end
end
# 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.
begin
if (latest_version != 'latest') && (Gem::Version.new(compare_version) > Gem::Version.new(local_version))
puts "#{cask_name} : latest version #{latest_version}, local version #{local_version}"
end
rescue
if (latest_version != 'latest') && (compare_version.strip != local_version.strip)
puts "#{cask_name} : latest version #{latest_version}, local version #{local_version}"
end
end
end
endSublime 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 2.app/Contents/SharedSupport/bin/subl" ~/bin/sublThen, 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.txtThe 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:
#!/bin/sh
open -a "/Applications/Sublime Text 2.app" $@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.
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.