Warning: Creating default object from empty value in /home/kellyrob/kellyrob99.com/blog/wp-content/plugins/search-unleashed/models/incoming-search.php on line 27

The Kaptain on … stuff

08 Dec, 2012

Kuler iTerm2 Themes with Groovy Scripting

Posted by: Kelly Robinson In: Development

Having recently purchased a new MBP laptop, I was going through the usual new computer activities and installing one of my favorite apps, the iTerm2 terminal program. This program really shines for managing multiple terminal windows, and recently they added the ability to easily import and export color themes for sharing. I, of course, got immediately distracted with a shiny new feature and ended up spending an afternoon writing some code to try it out. Along the way I grabbed some color themes from the Adobe Kuler site, built a quick and dirty SwingBuilder script to visualize the themes and wrote a script for emitting Apple plist files suitable for import into iTerm2.

Adobe Kuler

This is a nice resource for finding and building color themes primarily intended for web consumption. Each theme boils down to five colors represented as hexadecimal which can be applied to a layout design in a fairly predictable pattern. In addition to using the website directly, RSS feeds are made available for accessing shared themes. This makes grabbing a handful of themes for experimentation very easy to accomplish with the Groovy XmlSlurper.

Here’s an excerpt of the RSS feed we’re parsing, representing a five color theme named ‘Feeling Etsy’.

<item>
      <title>Theme Title: Feeling Etsy</title>
      <link>http://kuler.adobe.com/index.cfm#themeID/1892986</link>
      <guid>http://kuler.adobe.com/index.cfm#themeID/1892986</guid>
      <enclosure xmlns="http://www.solitude.dk/syndication/enclosures/">
        <title>Feeling Etsy</title>
        <link length="1" type="image/png">
          <url>http://kuler-api.adobe.com/kuler/themeImages/theme_1892986.png</url>
        </link>
      </enclosure>
      <description>
				 &lt;img src="http://kuler-api.adobe.com/kuler/themeImages/theme_1892986.png" /&gt;&lt;br /&gt;
				 
				 Artist: kenzia.studio&lt;br /&gt;
				 ThemeID: 1892986&lt;br /&gt;
				 Posted: 05/02/2012&lt;br /&gt;
				 
					 Tags: 
					 community...., join, lifestyle, share, vintage
				 &lt;br /&gt;	
				 
					Hex:
					DCEBDD, A0D5D6, 789AA1, 304345, AD9A27</description>

...

And here’s parsing code that queries for 100 of the ‘top rated’ and 100 of the ‘popular’ themes, serializing them out to a Groovy script file. There’s going to be some overlap in these two sets, so the end result is somewhat less than 200 themes.

/**
 * Reads RSS feeds from kuler and extracts the hexadecimal representation of each five element theme, writing those
 * values to a file.
 */
def feeds = [
        new URL("http://kuler-api.adobe.com/feeds/rss/get.cfm?itemsPerPage=100&listType=rating"),
        new URL("http://kuler-api.adobe.com/feeds/rss/get.cfm?itemsPerPage=100&listType=popular")
]
def mappedThemes = [:]
def slurp = {rssXML, themes ->
    def xml = new XmlSlurper().parseText(rssXML)
    xml.channel.item.each { theme ->
        println theme.title
        def desc = theme.description.toString().split('\n')
        def hex = desc[-1]
        hex = hex.replaceAll('\t', '')
        hex = hex.replaceAll(' ', '')
        themes.put(theme.title.toString().replaceAll('Theme Title:', '').trim().replaceAll(' ', '_')
                .replaceAll('\'', '_').toLowerCase(), hex.split(','))
    }
}

feeds.each { URL url ->
    slurp(url.text, mappedThemes)
}

println mappedThemes.keySet().size()

def themeMapFile = new File("kulerThemeMap-${new Date().format('yyMMddHHmmss')}.groovy")
themeMapFile << "themeMap = ${mappedThemes.inspect()}"
themeMapFile.absolutePath

The end result is a Groovy script file containing a single Map type variable named ‘themeMap’ in the global scope. This file can be interpreted by a GroovyShell to extract the Map of themes easily – not the best way to serialize the data but I actually wrote this parsing code a couple of years back and just wanted to quickly incorporate it into today’s efforts so I left it as is.

Output in the file is just a Map of the theme name to the five corresponding hexadecimal color codes.

themeMap = ['pie_party__for_all_kulerist!!!':['690011', 'BF0426', 'CC2738', 'F2D99C', 'E5B96F'],
 'pear_lemon_fizz':['04BFBF', 'CAFCD8', 'F7E967', 'A9CF54', '588F27'],
 'feeling_etsy':['DCEBDD', 'A0D5D6', '789AA1', '304345', 'AD9A27'],
 'phaedra':['FF6138', 'FFFF9D', 'BEEB9F', '79BD8F', '00A388'], 
...

Visualizing the Themes

In order to see which of these themes might look OK in iTerm I wrote a SwingBuilder script that reads in the script file output from the last step, like so:

assert args.size() == 1, '''The name or path to a file containing a themeMap 
script variable must be supplied on the command line'''

def themeMapFileName = args[0]
Binding binding = new Binding()
new GroovyShell(binding).evaluate(new File(themeMapFileName))
assert binding.hasVariable('themeMap'), "${args[0]} file must contain a Map variable named themeMap"
def themeMap = binding.themeMap as TreeMap

The Swing app just has a simple 2 column layout to display the name of the theme on the left and colored labels for each of the theme colors. Looks like this:

Having worked on Swing apps in plain-Jane Java professionally before, I’m still always astounded at how much less code you can write with Groovy and SwingBuilder. Here’s the 30 lines it takes to do the GUI and here’s the full file on github.

def swing = new groovy.swing.SwingBuilder()
def mainPanel = swing.panel() {
    boxLayout(axis: javax.swing.BoxLayout.Y_AXIS)
    label(text: "Showing ${themeMap.size()} themes")
    scrollPane() {
        panel() {
            boxLayout(axis: javax.swing.BoxLayout.Y_AXIS)
            themeMap.each { key, value ->
                panel(border:  emptyBorder(3)) {
                    gridLayout(columns: 2, rows: 1)
                    label(text: key)
                    value.each {
                        def color = Color.decode("#" + it)
                        int colorSize = 50
                        label(opaque: true, toolTipText: it, background: color, foreground: color,
                                preferredSize: [colorSize, colorSize] as Dimension,
                                border: lineBorder(color:Color.WHITE, thickness:1))
                    }
                }
            }
        }
    }
}
def frame = swing.frame(title: 'Frame') {
    scrollPane(constraints: SwingConstants.CENTER) {
        widget(mainPanel)
    }
}
frame.pack()
frame.show()

Creating the iTerm plist Files

The iTerm color presets define some ‘Basic Colors’ and some ‘ANSI Colors’. The basic ones cover: Foreground, Background, Bold, Selection, Selected Text, Cursor and Cursor Text. Seeing as we’ve got seven of these to map to five colors, I’ve(arbitrarily) chosen to make the Cursor and Cursor Text values depend upon the Foreground and Background colors, plus a fixed increment value. Here’s a picture to help explain.

And here is the configuration screen for this new profile in iTerm.

Each of the ANSI colors maps a standard color onto our color scheme and provides ‘Normal’ and ‘Bright’ variations for each color. I’ve (again arbitrarily) decided to map each of these by randomly selecting a color from the theme and determining a ‘Bright’ version of it. This leads to some themes where colors won’t show up very well if there is a conflict with the ‘Basic’ colors but it’s sufficient for my use case. In the end we want to write out an xml file for each theme which looks something like this:

<plist version="1.0">
  <dict>
    <key>Ansi 0 Color</key>
    <dict>
      <key>Blue Component</key>
      <real>0.2705882353</real>
      <key>Green Component</key>
      <real>0.2627450980</real>
      <key>Red Component</key>
      <real>0.1882352941</real>
    </dict>
    <key>Ansi 8 Color</key>
    <dict>
...

This repeating structure is easy to create using Groovy’s built in xml functionality. Because we want to include the DOCTYPE and xml header, I’m using StreamingMarkupBuilder and it’s handy yieldUnescaped function. Full source code is available on github, but here’s the bit which generates the two ANSI color definitions shown in the xml above.

final Closure buildColors = { builder, Color color ->
    builder.dict {
        key('Blue Component')
        real(normalize(color.blue))
        key('Green Component')
        real(normalize(color.green))
        key('Red Component')
        real(normalize(color.red))
    }
}

final Closure buildComponentColors = { builder, colors, i ->
    final hex = colors


    //Normal
    final Color color = extractColor(hex)
    builder.key("Ansi $i Color")
    buildColors(builder, color)

    //Bright
    final Color brighterColor = color.brighter()
    builder.key("Ansi ${i + 8} Color")
    buildColors(builder, brighterColor)
}

Each theme results in a PLIST xml file ready to import into iTerm.

Conclusion

I continue to be impressed with how easy Groovy makes it to solve common programming problems. Within a very small space of time I was able to:

  • parse multiple RSS feeds
  • create a Swing application to visualize results
  • transform the previously parsed results into an xml document usable in iTerm
  • do it all in a couple of hundred lines of code
  • publish the individual scripts on github

Hopefully this gives you some ideas on how to do some Groovy hacking of your own. I know it was a fun afternoon for me :)

Share

About

Tales of development, life and the folly that goes along with both.

Tags

profile for TheKaptain at Stack Overflow, Q&A for professional and enthusiast programmers
Get Adobe Flash player