Convert TextMate themes into iTerm 2 color schemes.
#!/usr/bin/env ruby | |
# | |
# This script is an astonishing feat of top notch | |
# rockstar craftsmanship. It totally uses artificial | |
# intelligence to extract colors out of tmTheme and | |
# build an itermcolors scheme file for iTerm2. | |
# | |
# I know this sounds crazy, but it actually knows | |
# approximately what colors should be used in the | |
# ANSI list, and tries to find nearest colors from | |
# the given tmTheme. This level of intelligent guessing | |
# has never been achieved before in the field of | |
# converting TextMate themes into iTerm color schemes. | |
# Never. | |
# | |
# | |
# USAGE | |
# | |
# Step 1. Make sure you have plist gem installed | |
# gem install plist | |
# | |
# Step 2. Make it executable | |
# chmod +x tm2iterm.rb | |
# | |
# Step 3. Execute it | |
# ./tm2iterm.rb /path/to/some.tmTheme /path/to/output.itermcolors | |
# | |
# Step 4. Double click the itermcolors file | |
# iTerm2 will pick up the scheme and add it to the list | |
require 'rubygems' | |
require 'plist' | |
BRIGHTEN_AMOUNT = 50 | |
class Color | |
attr_reader :hex, :rgb, :coefficients, :coef_hash | |
def initialize(hex = nil) | |
hex ||= '000000' | |
if hex =~ /\A#/ | |
hex.slice!(0) | |
end | |
@hex = hex[0..5] | |
@rgb = @hex.scan(/../).map do |hex_component| | |
cap_between_0_and_255(hex_component.to_i(16)) | |
end | |
@coefficients = @rgb.map do |value| | |
value / 255.0 | |
end | |
@coef_hash = { | |
'Red Component' => @coefficients[0], | |
'Green Component' => @coefficients[1], | |
'Blue Component' => @coefficients[2] | |
} | |
end | |
def brighten(value = BRIGHTEN_AMOUNT) | |
hex_string = @rgb.map do |component| | |
cap_between_0_and_255(component + value).to_s(16) | |
end.join | |
self.class.new(hex_string) | |
end | |
alias to_s hex | |
def inspect | |
"<Color:##{self}>" | |
end | |
def sum_distance(color) | |
[0, 1, 2].map{ |i| (color.rgb[i] - rgb[i]).abs }.inject(0){ |sum, v| sum += v } | |
end | |
def find_nearest(colors) | |
colors.min do |a, b| | |
sum_distance(a) <=> sum_distance(b) | |
end | |
end | |
def ==(color) | |
color.rgb == rgb | |
end | |
private | |
def cap_between_0_and_255(num) | |
num = [num, 0].max | |
num = [num, 255].min | |
num | |
end | |
end | |
# Shortcut | |
def c(hex) | |
Color.new(hex) | |
end | |
input, output = ARGV | |
if input.nil? || output.nil? | |
puts 'FAIL. OMG.' | |
puts 'Usage: tm2iterm /path/to/file.tmTheme /path/to/file.itermcolors' | |
exit(1) | |
elsif File.directory?(output) | |
output = File.absolute_path(output) + '/' + File.basename(input,'.tmTheme') + '.itermcolors' | |
end | |
tm_theme = Plist::parse_xml(input) | |
head = tm_theme['settings'].shift['settings'] | |
tail = Hash[tm_theme['settings'].map do |s| | |
[ s['name'].downcase, s['settings']['foreground'] ] | |
end] | |
background = c head['background'] | |
bold = c head['foreground'] | |
cursor = c head['caret'] | |
cursor_text = c head['foreground'] | |
foreground = c head['foreground'] | |
selected_text = c head['foreground'] | |
selection = c head['selection'] | |
# black #000000 | |
# red #ff0000 | |
# green #00ff00 | |
# yellow #ffff00 | |
# blue #0000ff | |
# magenta #ff00ff | |
# cyan #00ffff | |
# white #ffffff | |
ref_color_list = %w[ #000000 | |
#ff0000 | |
#00ff00 | |
#ffff00 | |
#0000ff | |
#ff00ff | |
#00ffff | |
#ffffff ].map{ |color| c(color) } | |
src_color_list = tail.values.uniq.map{ |color| c(color) } | |
ansi = [] | |
8.times do |i| | |
if matching_color = src_color_list.find{|color| color == ref_color_list[i]} | |
ansi[i] = matching_color | |
src_color_list.delete(matching_color) | |
end | |
end | |
8.times do |i| | |
ansi[i] = ansi[i] || begin | |
nearest = ref_color_list[i].find_nearest(src_color_list) | |
src_color_list.delete(nearest) | |
nearest | |
end | |
end | |
8.times do |i| | |
ansi[i + 8] = ansi[i].brighten | |
end | |
scheme = { | |
'Background Color' => background.coef_hash, | |
'Bold Color' => bold.coef_hash, | |
'Cursor Color' => cursor.coef_hash, | |
'Cursor Text Color' => cursor_text.coef_hash, | |
'Foreground Color' => foreground.coef_hash, | |
'Selected Text Color' => selected_text.coef_hash, | |
'Selection Color' => selection.coef_hash, | |
} | |
16.times do |i| | |
scheme.merge!("Ansi #{i} Color" => ansi[i].coef_hash) | |
end | |
File.open(output, 'w') do |file| | |
file.write(Plist::Emit.dump(scheme)) | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment