3from datetime import datetime, timezone
6from pathlib import Path
9from typing import TypedDict
11from jinja2 import Template
13from palette import Colours, Palette, enrich_colours
16def get_palette() -> tuple[str, Palette]:
18 Read the palette colours from `palette.json`.
20 with open("palette.json") as in_file:
21 data = json.load(in_file)
24 "light": enrich_colours(data["light"]),
25 "dark": enrich_colours(data["dark"]),
29def generate_textmate_theme(colours: Colours, palette_id: str) -> str:
31 Generate a TextMate theme based on my palette.
33 template = Template("""
34 // Generated from palette {{palette_id}} at {{now}}
35 // See https://github.com/alexwlchan/colour-scheme
37 \t\t{%- for block in settings %}
38 \t\t{\t{% for k, v in block.items() -%}
39 \t\t\t\t{{ k }} = {% if v is mapping %}{
40 \t\t\t\t{%- for kk, vv in v.items() %}
41 \t\t\t\t{{ kk }} = '{{ vv }}';{% endfor %}
42 \t\t\t};{% else %}'{{ v }}';
43 \t\t\t{% endif %}{% endfor %}
52 "foreground": colours["text"],
53 "background": colours["background"],
54 "caret": colours["text"],
55 "invisibles": colours["punctuation"],
56 "selection": colours["highlight"],
57 "lineHighlight": colours["highlight"],
64 "foreground": colours["text"],
65 "background": colours["background"],
69 "name": "Source base",
70 "scope": "source - source source",
72 "foreground": colours["text"],
73 "background": colours["background"],
79 ("Text base", "text"),
80 ("Source base", "source - source source"),
81 ("Embedded source (text)", "text meta.embedded"),
82 ("Embedded source (source)", "source meta.embedded"),
89 "foreground": colours["text"],
90 "background": colours["background"],
95 for scope, colour in [
96 ("comment", colours["comment"]),
97 ("source comment.block", colours["comment"]),
98 ("constant", colours["literal"]),
99 ("entity.name", colours["name"]),
100 ("variable", colours["name"]),
101 ("meta.class.ruby", colours["name"]),
102 ("keyword.control.class.ruby", colours["text"]),
103 ("meta.identifier.python", colours["name"]),
104 ("markup.heading.1.markdown", colours["name"]),
105 ("markup.heading.2.markdown", colours["name"]),
106 ("markup.heading.3.markdown", colours["name"]),
107 ("markup.heading.4.markdown", colours["name"]),
108 ("markup.heading.5.markdown", colours["name"]),
109 ("markup.heading.6.markdown", colours["name"]),
110 ("string", colours["string"]),
111 ("string constant.character.escape", colours["string"]),
112 ("string.interpolated", colours["string"]),
113 ("string.literal", colours["string"]),
114 ("string.interpolated constant.character.escape", colours["string"]),
117 {"name": scope, "scope": scope, "settings": {"foreground": colour}}
120 out = template.render(
122 palette_id=palette_id,
123 now=datetime.now(tz=timezone.utc).isoformat(),
125 out = textwrap.dedent(out)
130iTermColour = TypedDict(
133 "Red Component": float,
134 "Green Component": float,
135 "Blue Component": float,
136 "Alpha Component": float,
142def to_iterm2_colour(hex_string: str) -> iTermColour:
143 r_255 = int(hex_string[1:3], 16)
144 g_255 = int(hex_string[3:5], 16)
145 b_255 = int(hex_string[5:7], 16)
148 "Red Component": r_255 / 255,
149 "Green Component": g_255 / 255,
150 "Blue Component": b_255 / 255,
151 "Alpha Component": 1.0,
152 "Color Space": "SRGB",
156def generate_iterm2_theme(palette: Palette) -> bytes:
158 Generate an iTerm 2 theme based on my palette.
161 "Background Color (Dark)": to_iterm2_colour(palette["dark"]["background"]),
162 "Background Color (Light)": to_iterm2_colour(palette["light"]["background"]),
163 "Foreground Color (Dark)": to_iterm2_colour(palette["dark"]["text"]),
164 "Foreground Color (Light)": to_iterm2_colour(palette["light"]["text"]),
165 "Link Color (Dark)": to_iterm2_colour(palette["dark"]["blue"]),
166 "Link Color (Light)": to_iterm2_colour(palette["light"]["blue"]),
167 "Ansi 0 Color (Dark)": to_iterm2_colour(palette["dark"]["text"]),
168 "Ansi 0 Color (Light)": to_iterm2_colour(palette["light"]["text"]),
169 "Ansi 1 Color (Dark)": to_iterm2_colour(palette["dark"]["red"]),
170 "Ansi 1 Color (Light)": to_iterm2_colour(palette["light"]["red"]),
171 "Ansi 2 Color (Dark)": to_iterm2_colour(palette["dark"]["green"]),
172 "Ansi 2 Color (Light)": to_iterm2_colour(palette["light"]["green"]),
173 "Ansi 3 Color (Dark)": to_iterm2_colour(palette["dark"]["yellow"]),
174 "Ansi 3 Color (Light)": to_iterm2_colour(palette["light"]["yellow"]),
175 "Ansi 4 Color (Dark)": to_iterm2_colour(palette["dark"]["blue"]),
176 "Ansi 4 Color (Light)": to_iterm2_colour(palette["light"]["blue"]),
177 "Ansi 5 Color (Dark)": to_iterm2_colour(palette["dark"]["magenta"]),
178 "Ansi 5 Color (Light)": to_iterm2_colour(palette["light"]["magenta"]),
179 "Ansi 6 Color (Dark)": to_iterm2_colour(palette["dark"]["cyan"]),
180 "Ansi 6 Color (Light)": to_iterm2_colour(palette["light"]["cyan"]),
181 "Ansi 7 Color (Dark)": to_iterm2_colour(palette["dark"]["background"]),
182 "Ansi 7 Color (Light)": to_iterm2_colour(palette["light"]["background"]),
183 "Cursor Color (Light)": to_iterm2_colour(palette["light"]["text"]),
184 "Cursor Text Color (Light)": to_iterm2_colour(palette["light"]["highlight"]),
185 "Selection Color (Light)": to_iterm2_colour(palette["light"]["highlight"]),
187 "Alpha Component": 1.0,
188 "Blue Component": 0.45524293184280396,
190 "Green Component": 0.42884403467178345,
191 "Red Component": 0.36227649450302124,
193 "Ansi 10 Color (Dark)": {
194 "Blue Component": 0.4588235294117647,
195 "Color Space": "sRGB",
196 "Green Component": 0.43137254901960786,
197 "Red Component": 0.34509803921568627,
199 "Ansi 10 Color (Light)": {
200 "Blue Component": 0.4588235294117647,
201 "Color Space": "sRGB",
202 "Green Component": 0.43137254901960786,
203 "Red Component": 0.34509803921568627,
206 "Alpha Component": 1.0,
207 "Blue Component": 0.5097967982292175,
209 "Green Component": 0.47979283332824707,
210 "Red Component": 0.41305190324783325,
212 "Ansi 11 Color (Dark)": {
213 "Blue Component": 0.5137254901960784,
214 "Color Space": "sRGB",
215 "Green Component": 0.4823529411764706,
216 "Red Component": 0.396078431372549,
218 "Ansi 11 Color (Light)": {
219 "Blue Component": 0.5137254901960784,
220 "Color Space": "sRGB",
221 "Green Component": 0.4823529411764706,
222 "Red Component": 0.396078431372549,
225 "Alpha Component": 1.0,
226 "Blue Component": 0.5864985585212708,
228 "Green Component": 0.5783330798149109,
229 "Red Component": 0.5263533592224121,
231 "Ansi 12 Color (Dark)": {
232 "Blue Component": 0.5882352941176471,
233 "Color Space": "sRGB",
234 "Green Component": 0.5803921568627451,
235 "Red Component": 0.5137254901960784,
237 "Ansi 12 Color (Light)": {
238 "Blue Component": 0.5882352941176471,
239 "Color Space": "sRGB",
240 "Green Component": 0.5803921568627451,
241 "Red Component": 0.5137254901960784,
244 "Alpha Component": 1.0,
245 "Blue Component": 0.7465392351150513,
247 "Green Component": 0.44249439239501953,
248 "Red Component": 0.42710167169570923,
250 "Ansi 13 Color (Dark)": {
251 "Blue Component": 0.7686274509803922,
252 "Color Space": "sRGB",
253 "Green Component": 0.44313725490196076,
254 "Red Component": 0.4235294117647059,
256 "Ansi 13 Color (Light)": {
257 "Blue Component": 0.7686274509803922,
258 "Color Space": "sRGB",
259 "Green Component": 0.44313725490196076,
260 "Red Component": 0.4235294117647059,
263 "Alpha Component": 1.0,
264 "Blue Component": 0.6304863095283508,
266 "Green Component": 0.6296467781066895,
267 "Red Component": 0.5867035984992981,
269 "Ansi 14 Color (Dark)": {
270 "Blue Component": 0.6313725490196078,
271 "Color Space": "sRGB",
272 "Green Component": 0.6313725490196078,
273 "Red Component": 0.5764705882352941,
275 "Ansi 14 Color (Light)": {
276 "Blue Component": 0.6313725490196078,
277 "Color Space": "sRGB",
278 "Green Component": 0.6313725490196078,
279 "Red Component": 0.5764705882352941,
282 "Alpha Component": 1.0,
283 "Blue Component": 0.8977216482162476,
285 "Green Component": 0.9656357169151306,
286 "Red Component": 0.9873548150062561,
288 "Ansi 15 Color (Dark)": {
289 "Blue Component": 0.8901960784313725,
290 "Color Space": "sRGB",
291 "Green Component": 0.9647058823529412,
292 "Red Component": 0.9921568627450981,
294 "Ansi 15 Color (Light)": {
295 "Blue Component": 0.8901960784313725,
296 "Color Space": "sRGB",
297 "Green Component": 0.9647058823529412,
298 "Red Component": 0.9921568627450981,
301 "Alpha Component": 1.0,
302 "Blue Component": 0.2070317268371582,
304 "Green Component": 0.16550064086914062,
305 "Red Component": 0.053836725652217865,
307 "Ansi 8 Color (Dark)": {
308 "Blue Component": 0.21176470588235294,
309 "Color Space": "sRGB",
310 "Green Component": 0.16862745098039217,
311 "Red Component": 0.0,
313 "Ansi 8 Color (Light)": {
314 "Blue Component": 0.21176470588235294,
315 "Color Space": "sRGB",
316 "Green Component": 0.16862745098039217,
317 "Red Component": 0.0,
320 "Alpha Component": 1.0,
321 "Blue Component": 0.1624806821346283,
323 "Green Component": 0.3279757499694824,
324 "Red Component": 0.7377541661262512,
326 "Ansi 9 Color (Dark)": {
327 "Blue Component": 0.08627450980392157,
328 "Color Space": "sRGB",
329 "Green Component": 0.29411764705882354,
330 "Red Component": 0.796078431372549,
332 "Ansi 9 Color (Light)": {
333 "Blue Component": 0.08627450980392157,
334 "Color Space": "sRGB",
335 "Green Component": 0.29411764705882354,
336 "Red Component": 0.796078431372549,
339 "Alpha Component": 0.5,
340 "Blue Component": 0.14500156044960022,
342 "Green Component": 0.25274839997291565,
343 "Red Component": 0.9191656708717346,
345 "Badge Color (Dark)": {
346 "Alpha Component": 0.5,
347 "Blue Component": 0.0,
348 "Color Space": "sRGB",
349 "Green Component": 0.1491314172744751,
350 "Red Component": 1.0,
352 "Badge Color (Light)": {
353 "Alpha Component": 0.5,
354 "Blue Component": 0.0,
355 "Color Space": "sRGB",
356 "Green Component": 0.1491314172744751,
357 "Red Component": 1.0,
360 "Alpha Component": 1.0,
361 "Blue Component": 0.6304863095283508,
363 "Green Component": 0.6296467781066895,
364 "Red Component": 0.5867035984992981,
366 "Bold Color (Dark)": {
367 "Blue Component": 0.6313725490196078,
368 "Color Space": "sRGB",
369 "Green Component": 0.6313725490196078,
370 "Red Component": 0.5764705882352941,
372 "Bold Color (Light)": {
373 "Blue Component": 0.4588235294117647,
374 "Color Space": "sRGB",
375 "Green Component": 0.43137254901960786,
376 "Red Component": 0.34509803921568627,
379 "Alpha Component": 1.0,
380 "Blue Component": 0.5864985585212708,
382 "Green Component": 0.5783330798149109,
383 "Red Component": 0.5263533592224121,
385 "Cursor Color (Dark)": {
386 "Blue Component": 0.5882352941176471,
387 "Color Space": "sRGB",
388 "Green Component": 0.5803921568627451,
389 "Red Component": 0.5137254901960784,
391 "Cursor Guide Color": {
392 "Alpha Component": 0.25,
393 "Blue Component": 0.9907825589179993,
395 "Green Component": 0.9204942584037781,
396 "Red Component": 0.7486235499382019,
398 "Cursor Guide Color (Dark)": {
399 "Alpha Component": 0.25,
400 "Blue Component": 1.0,
401 "Color Space": "sRGB",
402 "Green Component": 0.9268307089805603,
403 "Red Component": 0.7021318674087524,
405 "Cursor Guide Color (Light)": {
406 "Alpha Component": 0.25,
407 "Blue Component": 1.0,
408 "Color Space": "sRGB",
409 "Green Component": 0.9268307089805603,
410 "Red Component": 0.7021318674087524,
412 "Cursor Text Color": {
413 "Alpha Component": 1.0,
414 "Blue Component": 0.2535606920719147,
416 "Green Component": 0.20825165510177612,
417 "Red Component": 0.08827308565378189,
419 "Cursor Text Color (Dark)": {
420 "Blue Component": 0.25882352941176473,
421 "Color Space": "sRGB",
422 "Green Component": 0.21176470588235294,
423 "Red Component": 0.027450980392156862,
425 "Match Background Color": {
426 "Alpha Component": 1.0,
427 "Blue Component": 0.32116127014160156,
429 "Green Component": 0.9860088229179382,
430 "Red Component": 0.9969714283943176,
432 "Match Background Color (Dark)": {
433 "Alpha Component": 1.0,
434 "Blue Component": 0.32116127014160156,
436 "Green Component": 0.9860088229179382,
437 "Red Component": 0.9969714283943176,
439 "Match Background Color (Light)": {
440 "Alpha Component": 1.0,
441 "Blue Component": 0.32116127014160156,
443 "Green Component": 0.9860088229179382,
444 "Red Component": 0.9969714283943176,
446 "Selected Text Color": {
447 "Alpha Component": 1.0,
448 "Blue Component": 0.6304863095283508,
450 "Green Component": 0.6296467781066895,
451 "Red Component": 0.5867035984992981,
453 "Selected Text Color (Dark)": {
454 "Blue Component": 0.6313725490196078,
455 "Color Space": "sRGB",
456 "Green Component": 0.6313725490196078,
457 "Red Component": 0.5764705882352941,
459 "Selected Text Color (Light)": {
460 "Blue Component": 0.4588235294117647,
461 "Color Space": "sRGB",
462 "Green Component": 0.43137254901960786,
463 "Red Component": 0.34509803921568627,
466 "Alpha Component": 1.0,
467 "Blue Component": 0.2535606920719147,
469 "Green Component": 0.20825165510177612,
470 "Red Component": 0.08827308565378189,
472 "Selection Color (Dark)": {
473 "Blue Component": 0.25882352941176473,
474 "Color Space": "sRGB",
475 "Green Component": 0.21176470588235294,
476 "Red Component": 0.027450980392156862,
480 buffer = io.BytesIO()
481 plistlib.dump(out, buffer, fmt=plistlib.FMT_BINARY)
482 return buffer.getvalue()
485if __name__ == "__main__":
486 palette_id, palette = get_palette()
488 out_dir = Path("out")
489 out_dir.mkdir(exist_ok=True)
491 (out_dir / "TextMate_light.tmTheme").write_text(
492 generate_textmate_theme(colours=palette["light"], palette_id=palette_id)
494 (out_dir / "TextMate_dark.tmTheme").write_text(
495 generate_textmate_theme(colours=palette["dark"], palette_id=palette_id)
497 (out_dir / "alexwlchan.itermcolors").write_bytes(generate_iterm2_theme(palette))