itc2kc, A really limited and silly project to convert a terminal theme
Back On My Bullshit Again (fixating on my terminal setup)
Another day, another rabbit trail setting up my terminal on a new machine, sigh... But at least this time I produced a small little project out of it! Basically my favorite terminal theme, Bliss, currently only has a terminal theme for iTerm2. I've been using kitty now as my daily driver for terminal emulation and I wanted to give it a fresh coat of bliss. The problem though is that these two terminal emulators have differing formats for their themes. iTerm2 uses an XML-based .itermcolors
file, while kitty uses a simple .conf
. The XML format is kind of a pain to work with so I wasn't super thrilled to dive into XML parsing again. Figured I'd throw it over to Claude for a starting point.
Claude's First Attempt
I don't love handing the controls over to AI immediately so the way I've been using these tools lately is when I don't want to relearn something from scratch that I've messed with in the past. In this case, I've used lxml
and the built-in Python XML module (although it's unclear if this is advisable1 any more?). To start here's the prompt I gave Claude:
I would like to convert an itermcolors file into a kitty.conf file. Could you use Python to write a short script to convert an .itermcolors to a kitty theme .conf file?
I'll write a Python script to convert an .itermcolors file (which is an XML format) to a kitty theme .conf file. This will help you use your iTerm2 color schemes in the kitty terminal emulator.
This script converts iTerm2 color schemes (.itermcolors files) to Kitty terminal configuration (.conf) files. Here's how to use it:
- Save the script as
itermcolors_to_kitty.py
- Make it executable:
chmod +x itermcolors_to_kitty.py
- Run it:
./itermcolors_to_kitty.py path/to/your/theme.itermcolors [optional_output_file.conf]
The script:
- Parses the XML structure of the iTerm2 color scheme
- Maps the iTerm2 color keys to their Kitty equivalents
- Converts the RGB values from 0-1 floats to hexadecimal format
- Generates a properly formatted Kitty theme configuration
If you don't specify an output file, it will create one with the same name as the input file but with a .conf extension. The output will be a fully functional Kitty theme that you can place in your Kitty configuration directory.
Note: This implementation uses
ElementTree.getnext()
which might require the lxml library. If you get an error, you can install it withpip install lxml
.
You can see the first result here.
Things I thought it did good on:
- Pretty good understanding of what needs to be converted between the two formats and how they could be translated
- Clear explanations to the different parts of the script
Things I thought it did bad on:
- Hallucinated a combination of
xml.etree
andlxml
APIs in code. Specifically importedxml.etree
but then usedgetnext()
method which is only inlxml
. - Hallucinated that an import error might occur with
xml.etree
. That's wrong, the import error could happen iflxml
is not installed
At this point, I wanted to give Claude another shot before I proceeded on my own.
Take 2
My next prompt was this:
This is a great structure, but the xml library used incorrect. Can you please rewrite this using lxml?2
You're right - the standard ElementTree doesn't have the
getnext()
method. Let me rewrite the script usinglxml
which will properly handle this XML structure.
I've rewritten the script to use
lxml
instead of the standard XML library. The main changes include:
- Changed the import from
xml.etree.ElementTree
tolxml.etree
- Completely rewrote the XML parsing logic to manually traverse the element tree instead of using
getnext()
- Added better error handling for invalid file formats
- Fixed the mapping for Bold Color to use
bold_color
in kitty's format
To use this script:
- Install the lxml library first:
pip install lxml
- Save the script as
itermcolors_to_kitty.py
- Make it executable:
chmod +x itermcolors_to_kitty.py
- Run it:
./itermcolors_to_kitty.py path/to/your/theme.itermcolors [optional_output_file.conf]
This implementation properly handles the plist XML structure of iTerm2 color schemes and should work with any standard .itermcolors file.
Same as before, code output for Round 2 can be found here
Things I thought it did good:
- It did correct its misuse of the two APIs together
Things I thought it did bad:
- Completely made up a configuration value for kitty (there's no
bold
value to my knowledge) - Incorrectly used the return from Element.xpath as if it were a single element. This function will return a list of Elements
- Rewrote the main logic for parsing the XML as if it were a C programmer (derogatory)3
- Removed its proper usage of
getnext()
from the previous iteration despite being told to uselxml
which is where that method comes from in the first place...🥲
Artisan Crafted Scripting
At this point, I decided that I had an okay scaffold to do the rest myself. Things that I added:
- Using
argparse
instead of working withsys.argv
directly - Used
itertools.batch
to group pairs of<dict>, <key>
to build the color dictionaries - Created a more efficient method of parsing XML with XPath expressions to gather RGB values
- Added linting, formatting, and type-checking to the script
I am particularly proud of the XPath part so I'm going to walk through the expression I came up with
Xtraordinary XPaths
The annoying thing about the .itermcolors
XML is that it has a flatter structure than I would expect. <keys>
are on the same level as their values so we have to parse the correct sibling instead of assuming the value is a child or the tag's text. Here's a snippet:
<key>Ansi 4 Color</key>
<dict>
<key>Alpha Component</key>
<real>1</real>
<key>Blue Component</key>
<real>1</real>
<key>Color Space</key>
<string>sRGB</string>
<key>Green Component</key>
<real>0.5980377197265625</real>
<key>Red Component</key>
<real>0.63416916131973267</real>
</dict>
We need to select the text in the <real>
tags corresponding to the RGB values. This is what I came up with after a bit of XPath learning:
key[text() = 'Red Component']/following-sibling::real[1]/text()
Note: s/Red/(Green|Blue)/ for those values respectively
This XPath string can be used from a color's <dict>
element. The way this expression works is by looking for a <key>
with text matching "Red Component". The expression then checks for the next <real>
tag that is a sibling of the matched <key>
and finally returns the text of the <real>
tag.
Praise For uv
A really quick section to praise uv
. I'm consistently amazed at how well this tool works for setting up Python projects. Every feature just works, and it all make sense. I wish more tooling were this good. I genuinely enjoy setting up Python projects now. I've been finding my standard setup for them to be:
uv
to manage Python, the project, dependencies, etcruff
to format/lintmypy
for type-checking
Setting these up before I ran my script for the first time really limited the few bugs/typos I had to a handful and they were very easy to diagnose.
The End!
This package is now published on PyPI. I hit a few snags due to a uv
misconfiguration on my part I'm open to PRs, feel free to send contributions.
There's a whole table of vulnerabilities in Python's docs about the built-in XML modules. I'm not an expert so hard to say how frequent these are encountered but they don't seem great at a glance!↩
Embarrassing typos remain because that's the level of transparency we're operating at for our dear readers of errno == -ERIC↩
I'm a C programmer myself so I'm allowed to say this↩