Red Artisan

Agile Ruby on Rails Specialists

Comma, CSV for all

posted by crafterm, 10 Mar 2009

CSV can be quite uninspiring at times, but as I’m sure many of you are all too familiar, many modern applications still require parsing and generation of CSV to interface with legacy systems and/or desktop software, notably Excel.

One of my Ruby on Rails clients required CSV data generation to support an ‘export to excel’ feature - so I embarked on a journey to look at the various CSV gems/plugins available at the time to export our data.

The result of this adventure gave birth to Comma, a small and simple (just over 60 lines implementation) gem that adds CSV generation support to arbitrary Ruby objects.

Using a declarative approach, you specify the output CSV format naming attributes, methods, associations, etc, all within a block with optional header names. Comma traverses these definitions to fetch model data, with conventions inferring headers when not specified using sensible defaults.

A few particular requirements during research led to Comma’s development:

Support pure Ruby objects

I wanted to export arbitrary instances to CSV, not just ActiveRecord derived objects, and hence didn’t want to use a plugin specific to Rails, or one that had internal knowledge of ActiveRecord or similar models for inferring information such as associations and attributes.

Flexibility

Transparency across associations, attributes and methods - they should all be treated the same. Some of the plugins I looked at required different configuration to name methods or associations to use, as opposed to attributes. I wanted to be able to cleanly define where the data for export should come from, and have Comma transparently access it (after all, Ruby’s #send mechanism provides the base foundations for this).

Multiple CSV output formats per class

One class we have requires several CSV output formats, one for delivery to end users, and another for escrow purposes. I wanted to be able to define multiple output formats per class, and be able to call upon them when required.

Integration

We’re using Ruby on Rails, so integration with Rails would be useful, particularly at the controller level, which should be DRY and able to ‘render :csv => @objects’.

Simplicity

CSV export shouldn’t be that hard on the plugin/gem implementer, nor the plugin/gem user - ideally I wanted to be able to define a CSV configuration (with an optional name) using a declarative syntax that names what should be exported, and have that same definition used for data access and header name generation.

An example use of Comma follows:

class Book < ActiveRecord::Base

  # ================
  # = Associations =
  # ================
  has_many   :pages
  has_one    :isbn
  belongs_to :publisher

  # ===============
  # = CSV support =
  # ===============
  comma do

    name
    description

    pages :size => 'Pages'
    publisher :name
    isbn :number_10 => 'ISBN-10', :number_13 => 'ISBN-13'
    blurb 'Summary'

  end

end

Annotated, the ‘Comma’ description includes:

# starts a Comma description block, generating 2 methods #to_comma and #to_comma_headers for this class.
comma do

  # name, description are attributes of Book with the header being reflected as 'Name', 'Description'
  name
  description

  # pages is an association returning an array, :size is called on the association results, with the header name specifed as 'Pages'
  pages :size => 'Pages'

  # publisher is an association returning an object, :name is called on the associated object, with the reflected header 'Name'
  publisher :name

  # isbn is an association returning an object, :number_10 and :number_13 are called on the object with the specified headers 'ISBN-10' and 'ISBN-13'
  isbn :number_10 => 'ISBN-10', :number_13 => 'ISBN-13'

  # blurb is an attribute of Book, with the header being specified directly as 'Summary'
  blurb 'Summary'

end

Notice above how attributes and associations are all specified and treated the same, header names are reflected from the method names using sensible conventions unless provided directly, and more complex combinations of data can be grouped together into methods if required.

Multiple descriptions can be specified with a named Comma block:

# ===============
# = CSV support =
# ===============
comma do  # implicitly named :default

  name
  description

  pages :size => 'Pages'
  publisher :name
  isbn :number_10 => 'ISBN-10', :number_13 => 'ISBN-13'
  blurb 'Summary'

end

comma :brief do

  name
  description
  blurb 'Summary'

end

You can specify which format you’d prefer as an optional parameter to #to_comma.

If you’re using Ruby on Rails, your controllers automatically gain Comma-fu.

class BooksController < ApplicationController

  def index
    respond_to do |format|
      format.csv { render :csv => Book.limited(50) }
    end
  end

end

Comma is licensed under the MIT License, and can be installed directly from github’s gem server.

sudo gem install crafterm-comma

Please feel free to contact me if you have any questions and/or feedback regarding Comma.

Distributed Image Processing with Airbrush

posted by crafterm, 02 Nov 2008

Airbrush is a lightweight distributed processing tool, that Ruby/Rails applications can use to communicate with and offload heavy processing of images, and/or other tasks while they continue processing requests.

Early this year, one of my clients started experiencing issues with their Rails application that managed the content for approximately 40 sites. The problem was in the area of image processing, with very large images, many several hundred megabytes in size that would be uploaded by the site's administrators for publishing on various sorts of media.

When an image was uploaded several previews were being generated, and this process was bringing the system to a halt, consuming all resources of the Mongrel processing the request, to the point where the virtual server hosting that process would kill it off as a rampant process. Even testing some of the offending images would bring our MacBook Pro's to a grinding halt, with memory use soaring into swap, causing everything to slow down to a snails pace.

We employed various tools for the platforms we were testing on, strace under Linux and later dtrace under Mac OS X. We noticed one example image, 10mb in size, would allocate 700mb of memory while it was being read by the image library (RMagick 2.x at the time). Image Science and even Quartz on my Mac OS X Tiger install exhibited similar behaviour.

After much research and testing, we found many of the offending images to be in non-RGB colour profiles, and to include all sorts of meta data (one even included an entire XML formatted Mac OSX plist file in its header). Installing profile management and pre-processing metadata alleviated much of the memory exhaustion pain.

The production environment consisted of many smaller Ubuntu Linux virtual private servers (~256mb ram, etc), so we decided to isolate the processing of images into a dedicated slice, so that could be shared across all the application servers, and could be configured to any specifications we required to handle the scale content being rendered.

This gave birth to Airbrush, which has been in use now for several months now with great success, and Square Circle Triangle, the client who paid for its development, has allowed us to open source Airbrush for all to use under the MIT license.

Airbrush was designed to abstract the three main roles in its architecture - the listening, the processing and the publishing of results from incoming jobs. This was done primarily to allow us to provide a wide range of access to Airbrush's services now and in the future (eg. a queuing system, webservice, etc), and to allow processing of any job type, not just related to image processing (eg. bulk emailing, report generation, etc).

Around the time Airbrush was architected, Starling, a memcache derived persistent queue implementation was released, and became the perfect fit for Airbrush's first listener implementation.

Getting Started

To get up and running with Airbrush, first install the following gems:

$> gem install starling airbrush rmagick

Then, create a memcache queue using Starling (specifying the queue and pid file locations):

$> starling -q /var/tmp/starling -P /var/tmp/starling.pid
I, [2008-11-02T11:58:13.443012 #77820]  INFO -- : Starling STARTUP on 127.0.0.1:22122

Then, start any number of Airbrush server instances:

$> airbrush -v
Sun Nov 02 11:58:22 +1100 2008: Accepting incoming jobs

'v' indicates verbose operation, so that you receive extra logging information. Several other options can be passed to Airbrush such as the memcache server location and port, job poll frequency and a log target, run 'airbrush -h' for further details.

Both Starling and Airbrush default to the localhost as the memcache server on port 22122, so if you are running both Starling and Airbrush locally the defaults will be fine.

To send a preview request to an Airbrush server, you can use the example airbrush-example-client command included with the Airbrush gem:

$> airbrush-example-client -i leaves_desktop.jpg -o resized
Sending leaves_desktop.jpg for preview processing

This sends the image 'leaves_desktop.jpg' to be resized into two smaller previews, with filenames starting with 'resized'. The example client polls for results returned by the preview job requested - in production, depending on the job and production requirements, you have the flexibility to poll, block or continue processing and allow the job to be processed in the background.

Back on the Airbrush server side, you'll notice some further logging output when this happens:

Sun Nov 02 12:02:16 +1100 2008: Processing generate-previews
Sun Nov 02 12:02:18 +1100 2008: Processed previews ({:filename=>"leaves_desktop.jpg", :sizes=>{:small=>[300], :large=>[600]}, :image=>"[FILTERED]"})
Sun Nov 02 12:02:18 +1100 2008: Published results from generate-previews
Sun Nov 02 12:02:18 +1100 2008: Processed generate-previews: 0.806346 seconds processing time

Indicating a successful job. Should an error occur, both the client and server will report what happened.

API

Internally, the API request to create several image previews is:

client = Airbrush::Client.new(memcache_host)
client.process(
  'generate-previews', :previews, 
    :image => File.read(OPTIONS[:image]), 
    :sizes => { :small => [300], :large => [600] } )

This creates an instance of the Airbrush client, and instructs it to process a given job via the #process method. The parameters to #process specify a unique job id (also used as the return memcache queue name for any results the job may provide), the job name (:previews in this case), and arguments to the job (two sizes in this example, for the creation of 'small' and 'large' previews, with a longest edge of 300 and 600 pixels respectively). Airbrush will calculate the resultant dimensions using the aspect ratio of the image.

An Airbrush server reads this job request from the Starling memcache queue, processes it and places the results back on the queue for the client to read either immediately, or at a later time.

Processing is a simple Ruby class with method names matching job names, here's an example taken from Airbrush's RMagick based image processor with the resize and previews job implementations:

module Airbrush
  module Processors
    module Image
      class Rmagick < ImageProcessor
        filter_params :image # ignore any argument called 'image' in any logging

        def resize(image, width, height = nil)
          width, height = calculate_dimensions(image, width) unless height

          process image do
            change_geometry("#{width}x#{height}") { |cols, rows, image| image.resize!(cols, rows) }
          end
        end

        def previews(image, sizes) # sizes => { :small => [200,100], :medium => [400,200], :large => [600,300] }
          sizes.inject(Hash.new) { |m, (k, v)| m[k] = crop_resize(image, *v); m }
        end

        # ... snip ...

      end
    end
  end
end

The parameters to each method are extracted from the options has passed in as arguments to the named job (similar to how Merb can extract values from the params[] hash automatically).

The current RMagick based image processor supports resizing, cropping and multiple preview generation, with images in RGB and CMYK colour profiles. 'before' and 'after' filters are also supported to pre or post process images (eg to add a watermark or filter out metadata amongst other things) as well. A Core Image based processor is also available.

The distributed nature of memcache allows us to daisy chain as many airbrush servers as we'd like to handle anticipated load, and we can spread this across any number of VPS or real servers as we'd like (even other platforms such as X-Serve's with dedicated video processing hardware).

Another added benefit of using Starling to handle the incoming job queue is that Airbrush servers can be increased/decreased without affecting the queue reliability - even when no Airbrush servers are running, jobs will simply be added to the Starling queue and wait until one is started.

Airbrush is available as a gem on GitHub and Rubyforge, and is under the MIT license. We're excited to see it released, and keen to continue its development. Please also feel free to contact us with any suggestions or ideas to make Airbrush more useful for everyone.

Sprinkle Update!

posted by crafterm, 03 Aug 2008

Sprinkle, the new provisioning tool I recently released has been progressing really well over the past few months. Its been great to see the focus of Sprinkle to succinctly describe software installation via an elegant Ruby DSL language improve so well.

Last Thursday at our monthly Melbourne Ruby User Group meetup, I had the pleasure of demonstrating Sprinkle to the local community. I've uploaded the slides for those interested:

Gem packages for Sprinkle are available from both GitHub and Rubyforge, and include several new features that have been added sine my original post about the project.

Deployment/command delivery

  • Support for Vlad in addition to Capistrano, allowing you to leverage upon your existing Vlad configuration

  • Direct or gateway tunneled net/ssh connection support, allowing you to configure Sprinkle to communicate with remote hosts via net/ssh directly if you desire

  • Support for provisioning your local machine using Sprinkle by running any commands locally

Installers

  • Support for automatically verifying installations pre- and post-install to detect any installation errors, and also to optimize the installation order to skip packages already installed.

  • Support for installing build dependencies of APT packages

  • Support for installing RPM packages

  • Support for installing GEM packages from a specific remote repository (eg. github) and into a specific local repository

Documentation

  • Full RDoc coverage across all classes, also published online

  • New blog posts & screencast demonstrating Sprinkle

Examples

  • Many new examples have been contributed showing several ways of using Sprinkle to provision packages from Git, Ruby, Rails, Sphinx, through to Phusion, etc.

Plus many other contributions from people around the world helping with specs, bugs and other improvements which has been great.

I'd really like to thank everyone who has jumped in and helped with the project in any way over the past few months, I really appreciate your support!

Sprinkle Some Powder!

posted by crafterm, 27 May 2008

Provisioning a brand new server or VPS slice can be quite tricky, tedious and time consuming, particularly if done manually with changing software versions and configurations.

In the Rails world, most of us are using virtual private servers which are instantiated from base operating system images, it takes only a few minutes to create a slice, however installing the rest of your server's stack, be it Rails, Merb or another framework is where the work begins. Provisioning in this sense, is installing all software required post operating system install.

Sprinkle is a new prototype tool that you can use to provision your servers/slices. Its declarative policy/state based approach for specifying how a remote system should be provisioned with intelligent logic to support dependencies, multiple installer types and remote installation is really compelling.

Several free and commercially available tools already exist to help automate the installation of software however most fall into two styles of design:

1 - Task based, where the tool issues a list of commands to run on the remote system, either remotely via a network connection or smart client.
2 - Policy/state based, where the tool determines what needs to be run on the remote system by examining its current and final state.

Task based solutions are usually quite easy and fast to get up and running, but can be problematic as the user has to define all of the commands manually (not to mention get them right with testing). Policy/state based solutions have much more intelligence about how to modify and adapt the remote system, but often require specialized software to run remotely.

Sprinkle is a prototype tool I've been working on recently in this space that merges both concepts together, using a Ruby domain specific language to declaratively describe the state of the remote system. Using Sprinkle, provisioning your brand new remote server or slice can be automated using pre-defined and/or customized scripts from a single machine at your fingertips.

Sprinkle reads a script that defines a set of packages, a set of policies that define what packages should be installed on what roles of target machines, and a deployment section that defines the delivery mechanism for communicating with remote machines, and any default settings.

Packages can have relationships between each other to support dependencies. Virtual packages are also supported allowing you to define a role that a package (or multiple) fulfills, with the user or Sprinkle selecting which concrete package should be used at runtime.

Packages can also support arbitrary installer types, allowing you to install packages from source, gems, apt, or any other installer you'd like to employ. Installer types know what commands need to be issued to install packages, so all that needs to be specified in a script is the installer type and metadata about the package itself.

In essence, Sprinkle is about defining a domain specific meta-language for describing and processing the installation of software.

Example Sprinkle Script

Here's an example Sprinkle deployment script, annotated to explain each section:

# Annotated Example Sprinkle Rails deployment script
#
# This is an example Sprinkle script configured to install Rails from Gems, Apache, Ruby and
# Sphinx from source, and MySQL from APT on an Ubuntu system.
#
# Installation is configured to run via capistrano (and an accompanying deploy.rb recipe script).
# Source based packages are downloaded and built into /usr/local on the remote system.
#
# A sprinkle script is separated into 3 different sections. Packages, policies and deployment:


# Packages (separate files for brevity)
#
#  Defines the world of packages as we know it. Each package has a name and
#  set of metadata including its installer type (eg. apt, source, gem, etc). Packages can have
#  relationships to each other via dependencies.

require 'packages/essential'
require 'packages/rails'
require 'packages/database'
require 'packages/server'
require 'packages/search'


# Policies
#
#  Names a group of packages (optionally with versions) that apply to a particular set of roles:
#
#   Associates the rails policy to the application servers. Contains rails, and surrounding
#   packages. Note, appserver, database, webserver and search are all virtual packages defined above.
#   If there's only one implementation of a virtual package, it's selected automatically, otherwise
#   the user is requested to select which one to use.

policy :rails, :roles => :app do
  requires :rails, :version => '2.0.2'
  requires :appserver
  requires :database
  requires :webserver
  requires :search
end


# Deployment
#
#  Defines script wide settings such as a delivery mechanism for executing commands on the target
#  system (eg. capistrano), and installer defaults (eg. build locations, etc):
#
#   Configures sprinkle to use capistrano for delivery of commands to the remote machines (via
#   the named 'deploy' recipe). Also configures 'source' installer defaults to put package gear
#   in /usr/local

deployment do

  # mechanism for deployment
  delivery :capistrano do
    recipes 'deploy'
  end

  # source based package installer defaults
  source do
    prefix   '/usr/local'
    archives '/usr/local/sources'
    builds   '/usr/local/build'
  end

end

# End of script, given the above information, Spinkle will apply the defined policy on all roles using the
# deployment settings specified.

Given such a script, Sprinkle will apply the defined policy rails on the target machines identified by the role app, where the policy rails is composed of packages for the rails gem itself, an application server, webserver, search daemon, and the ruby runtime.

Currently, Sprinkle uses Capistrano internally for communicating with remote systems, however this is pluggable as well, allowing for just about any concievable delivery mechanism in the future. The deployment section above identifies Capistrano as the delivery mechanism, specifying a local deploy.rb script that defines what roles are available, and what machines are defined within those roles.

This particular script breaks the package section up into multiple files, here are some of the actual package definitions (complete example available here):

package :ruby do
  description 'Ruby Virtual Machine'
  version '1.8.6'
  source "ftp://ftp.ruby-lang.org/pub/ruby/1.8/ruby-#{version}-p111.tar.gz" # implicit :style => :gnu
  requires :ruby_dependencies
end

package :rubygems do
  description 'Ruby Gems Package Management System'
  version '1.0.1'
  source "http://rubyforge.org/frs/download.php/29548/rubygems-#{version}.tgz" do
    custom_install 'ruby setup.rb'
  end
  requires :ruby
end

package :rails do
  description 'Ruby on Rails'
  gem 'rails'
  version '2.0.2'
end

package :sphinx, :provides => :search do
  description 'MySQL full text search engine'
  version '0.9.8-rc2'
  source "http://www.sphinxsearch.com/downloads/sphinx-#{version}.tar.gz"
  requires :mysql_dev
end

package :apache, :provides => :webserver do
  description 'Apache 2 HTTP Server'
  version '2.2.6'
  source "http://apache.wildit.net.au/httpd/httpd-#{version}.tar.bz2" do
    enable %w( mods-shared=all proxy proxy-balancer proxy-http rewrite cache headers ssl deflate so )
    prefix "/opt/local/apache2-#{version}"
    post :install, 'install -m 755 support/apachectl /etc/init.d/apache2', 'update-rc.d -f apache2 defaults'
  end
  requires :apache_dependencies
end

Each package includes a description, optional version, optional list of dependencies and an installer type (also optional allowing for meta-packages).

Source installers are particularly intelligent and will download, configure and install source archives from a remote location directly on the target machine. They assume GNU style source archives by default (ie. tar.gz/tar.bz2 compressed archives, configure script and make, make install style semantics), however are completely customziable to support any arbitrary build style (rubygems for example does this above), with pre and post commands.

The Apache installer for example, specifies a few extra source installer options such as a set of --enable options, an alternate installation prefix and a series of post installation commands to be executed.

With this example configuration, lets take a look at actually using Sprinkle to provision a remote server.

Usage

Sprinkle supports several command line options:

Usage
=====

$> sprinkle [options]

Options are:

  -s, --script=PATH                Path to a sprinkle script to run
  -t, --test                       Process but don't perform any actions
  -v, --verbose                    Verbose output
  -c, --cloud                      Show powder cloud, ie. package hierarchy and installation order
  -h, --help                       Show this help message.

where you can name the script to be procesed, enable testing mode or verbose output, and/or examine the cloud of packages and operations that will be performed.

Viewing the powder cloud!

Sprinkle calculates all operations to be performed on remote servers upfront which is nice, as it allows you to inspect what modifications will be made to the system before any are actually performed. Lets inspect the powder (ie. package) cloud for the above script:

$> sprinkle -c -t -s rails.rb
--> Cloud hierarchy for policy rails

Policy rails requires package rails
        Package rails requires rubygems
                Package rubygems requires build_essential
                Package rubygems requires ruby
                        Package ruby requires build_essential
                        Package ruby requires ruby_dependencies

Policy rails requires package appserver
Selecting mongrel_cluster for virtual package appserver
        Package mongrel_cluster requires rubygems
                Package rubygems requires build_essential
                Package rubygems requires ruby
                        Package ruby requires build_essential
                        Package ruby requires ruby_dependencies
        Package mongrel_cluster requires mongrel
                Package mongrel requires rubygems
                        Package rubygems requires build_essential
                        Package rubygems requires ruby
                                Package ruby requires build_essential
                                Package ruby requires ruby_dependencies

Policy rails requires package database
Selecting mysql for virtual package database

Policy rails requires package webserver
Selecting apache for virtual package webserver
        Package apache requires build_essential
        Package apache requires apache_dependencies

Policy rails requires package search
Selecting sphinx for virtual package search
        Package sphinx requires build_essential
        Package sphinx requires mysql_dev

--> Normalized installation order for all packages: build_essential, ruby_dependencies, ruby, rubygems, rails, mongrel, mongrel_cluster, mysql, apache_dependencies, apache, mysql_dev, sphinx

-c indicates that Sprinkle should print the powder cloud (ie. the output above)
-t indicates that we're operating in test mode, so we won't actually perform any remote commands
-s identifies the Sprinkle script that should be processed

Above we can see that the policy rails required packages rails, appserver, database, webserver and search.

Note that all of these packages bar rails are actually virtual packages, so Sprinkle has selected an appropriate implementation of each virtual package automatically based on the supplied package definitions. If more than one package provided an implementation of a virtual package, then the user would be given the opportunity to select which one they prefer.

Under each package is a textual representation of that package's dependency tree, including all sub-dependencies, etc. Dependencies are packages that need to be installed first before a higher level package can be installed.

You'll notice that several packages have the same dependencies, eg. both rails and mongrel require ruby, which has its own dependencies as well. Sprinkle will install all packages in reverse dependency order so that lower level dependencies are installed before higher level packages, and it will also normalize the final package list to remove duplicates so that packages aren't installed multiple times unnecessarily. This is the final line in the output above which lists the actual packages to be installed and order of installation.

Provisioning a remote system

To actually provision a remote server we simply remove the testing (and if desired cloud) flags from the command issued above and Sprinkle will process the configuration and provision the remote system. Note for the moment, you'll need to ensure that your SSH keys are appropriately installed on the remote server under a user that has enough privileges to install software (generally the root user):

$> sprinkle -s rails.rb
--> Installing build_essential for roles: app
--> Installing ruby_dependencies for roles: app
--> Installing ruby for roles: app
--> Installing rubygems for roles: app
--> Installing rails for roles: app
--> Installing mongrel for roles: app
--> Installing mongrel_cluster for roles: app
--> Installing mysql for roles: app
--> Installing apache_dependencies for roles: app
--> Installing apache for roles: app
--> Installing mysql_dev for roles: app
--> Installing sphinx for roles: app

(its also possible to put the #!/usr/bin/env sprinkle -c line at the top of a sprinkle script and make it executable).

After the command is finished, all of the requested software will have been applied on your target system.

If you'd like to see more action printed as commands are run, specify the --verbose (-v) flag. Internally, Capistrano tasks are dynamically defined and executed at runtime for each package's installation, using a Capistrano configuration file to identify the actual roles and hostnames associated with those roles to communicate with. The verbose option will display Capistrano activity in addition to the usual Sprinkle output.

An extra benefit of leveraging Capistrano is that you can actually provision multiple servers/slices simultaneously and in parallel if desired.

I want!

If you're interested in downloading and experimenting with Sprinkle, you can clone and/or watch the project at GitHub, or download it from GitHub's gem server using:

$> sudo gem install crafterm-sprinkle --source http://gems.github.com/

The official Rubyforge gem server will also be updated over the coming days as well. If you download the source, you can create a gem package for installation by:

$> rake package
$> sudo gem install -l pkg/sprinkle-0.1.0

There are also specs with a decent amount of coverage over the code base that you can run as well:

$> rake spec

Prerequsites

Installing the Sprinkle gem will also install all pre-requsite gems such as activesupport, highline and capistrano. The only other pre-requisite is that you have SSH connectivity to the remote system you wish to provision, preferably with SSH keys in place to prevent passwords being asked for.

Finally

Sprinkle is a young project and while operational still in development, with several limitations. Currently, only Ubuntu/Debian has been tested as a target deployment platform, operating system abstraction and other platforms will be tested and supported in the future, along with several new features that are in the pipeline.

I'm most certainly open to ideas, suggestions and thoughts about how Sprinkle can be improved and generally made better for the community, and I really welcome any bug reports, patches and suggestions. Please feel free to contact me with any comments at all.

Special Thanks!

Several people have been really helpful during the development of Sprinkle. In particular I'd like to thank Ben Schwarz and Pete Yandell for their initial feedback and help after my first demos. I'd also really like to thank Matthew and Jared from Slicehost for their help and support as well.

Twilight Theme for Emacs!

posted by crafterm, 27 May 2008

Recently I’ve been returning to my Unix heritage and using Emacs as my day to day editor. Its been quite an enlightening journey returning to such a powerful editor with its limitless customization and features (probably worthy of a whole separate post), although one things I do miss from TextMate are the colour themes used for syntax highlighting.

Emacs has a package called color-theme which can be used to customize font faces/colours however in comparison to the styled themes TextMate offers, most of default ones available don’t quite compare.

In TextMate I used the Twilight theme quite often, so, after examining the color-theme package format, I’ve ported across the Twilight theme to Emacs, and have released it as a small project on GitHub.

Its definitely a work in progress, most of the colours within Ruby mode are working as expected, but please feel free to improve anything that needs some fine tuning (eg. Rails keywords, and particularly non-Ruby modes such as dired, ERB, SCM, etc). Patches are most certainly welcome.