Skip to main content

generate_palette_files.py

1#!/usr/bin/env python3
3from datetime import datetime, timezone
4import io
5import json
6from pathlib import Path
7import plistlib
8import textwrap
9from typing import TypedDict
11from jinja2 import Template
13from palette import Colours, Palette, enrich_colours
16def get_palette() -> tuple[str, Palette]:
17 """
18 Read the palette colours from `palette.json`.
19 """
20 with open("palette.json") as in_file:
21 data = json.load(in_file)
23 return data["id"], {
24 "light": enrich_colours(data["light"]),
25 "dark": enrich_colours(data["dark"]),
26 }
29def generate_textmate_theme(colours: Colours, palette_id: str) -> str:
30 """
31 Generate a TextMate theme based on my palette.
32 """
33 template = Template("""
34 // Generated from palette {{palette_id}} at {{now}}
35 // See https://github.com/alexwlchan/colour-scheme
36 {\tsettings = (
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 %}
44 \t\t},{% endfor %}
45 \t);
46 }
47 """)
49 settings = [
50 {
51 "settings": {
52 "foreground": colours["text"],
53 "background": colours["background"],
54 "caret": colours["text"],
55 "invisibles": colours["punctuation"],
56 "selection": colours["highlight"],
57 "lineHighlight": colours["highlight"],
58 }
59 },
60 {
61 "name": "Text base",
62 "scope": "text",
63 "settings": {
64 "foreground": colours["text"],
65 "background": colours["background"],
66 },
67 },
68 {
69 "name": "Source base",
70 "scope": "source - source source",
71 "settings": {
72 "foreground": colours["text"],
73 "background": colours["background"],
74 },
75 },
76 ]
78 for name, scope in [
79 ("Text base", "text"),
80 ("Source base", "source - source source"),
81 ("Embedded source (text)", "text meta.embedded"),
82 ("Embedded source (source)", "source meta.embedded"),
83 ]:
84 settings.append(
85 {
86 "name": name,
87 "scope": scope,
88 "settings": {
89 "foreground": colours["text"],
90 "background": colours["background"],
91 },
92 }
93 )
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"]),
115 ]:
116 settings.append(
117 {"name": scope, "scope": scope, "settings": {"foreground": colour}}
118 )
120 out = template.render(
121 settings=settings,
122 palette_id=palette_id,
123 now=datetime.now(tz=timezone.utc).isoformat(),
124 )
125 out = textwrap.dedent(out)
126 out = out.strip()
127 return out
130iTermColour = TypedDict(
131 "iTermColour",
132 {
133 "Red Component": float,
134 "Green Component": float,
135 "Blue Component": float,
136 "Alpha Component": float,
137 "Color Space": str,
138 },
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)
147 return {
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",
153 }
156def generate_iterm2_theme(palette: Palette) -> bytes:
157 """
158 Generate an iTerm 2 theme based on my palette.
159 """
160 out = {
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"]),
186 "Ansi 10 Color": {
187 "Alpha Component": 1.0,
188 "Blue Component": 0.45524293184280396,
189 "Color Space": "P3",
190 "Green Component": 0.42884403467178345,
191 "Red Component": 0.36227649450302124,
192 },
193 "Ansi 10 Color (Dark)": {
194 "Blue Component": 0.4588235294117647,
195 "Color Space": "sRGB",
196 "Green Component": 0.43137254901960786,
197 "Red Component": 0.34509803921568627,
198 },
199 "Ansi 10 Color (Light)": {
200 "Blue Component": 0.4588235294117647,
201 "Color Space": "sRGB",
202 "Green Component": 0.43137254901960786,
203 "Red Component": 0.34509803921568627,
204 },
205 "Ansi 11 Color": {
206 "Alpha Component": 1.0,
207 "Blue Component": 0.5097967982292175,
208 "Color Space": "P3",
209 "Green Component": 0.47979283332824707,
210 "Red Component": 0.41305190324783325,
211 },
212 "Ansi 11 Color (Dark)": {
213 "Blue Component": 0.5137254901960784,
214 "Color Space": "sRGB",
215 "Green Component": 0.4823529411764706,
216 "Red Component": 0.396078431372549,
217 },
218 "Ansi 11 Color (Light)": {
219 "Blue Component": 0.5137254901960784,
220 "Color Space": "sRGB",
221 "Green Component": 0.4823529411764706,
222 "Red Component": 0.396078431372549,
223 },
224 "Ansi 12 Color": {
225 "Alpha Component": 1.0,
226 "Blue Component": 0.5864985585212708,
227 "Color Space": "P3",
228 "Green Component": 0.5783330798149109,
229 "Red Component": 0.5263533592224121,
230 },
231 "Ansi 12 Color (Dark)": {
232 "Blue Component": 0.5882352941176471,
233 "Color Space": "sRGB",
234 "Green Component": 0.5803921568627451,
235 "Red Component": 0.5137254901960784,
236 },
237 "Ansi 12 Color (Light)": {
238 "Blue Component": 0.5882352941176471,
239 "Color Space": "sRGB",
240 "Green Component": 0.5803921568627451,
241 "Red Component": 0.5137254901960784,
242 },
243 "Ansi 13 Color": {
244 "Alpha Component": 1.0,
245 "Blue Component": 0.7465392351150513,
246 "Color Space": "P3",
247 "Green Component": 0.44249439239501953,
248 "Red Component": 0.42710167169570923,
249 },
250 "Ansi 13 Color (Dark)": {
251 "Blue Component": 0.7686274509803922,
252 "Color Space": "sRGB",
253 "Green Component": 0.44313725490196076,
254 "Red Component": 0.4235294117647059,
255 },
256 "Ansi 13 Color (Light)": {
257 "Blue Component": 0.7686274509803922,
258 "Color Space": "sRGB",
259 "Green Component": 0.44313725490196076,
260 "Red Component": 0.4235294117647059,
261 },
262 "Ansi 14 Color": {
263 "Alpha Component": 1.0,
264 "Blue Component": 0.6304863095283508,
265 "Color Space": "P3",
266 "Green Component": 0.6296467781066895,
267 "Red Component": 0.5867035984992981,
268 },
269 "Ansi 14 Color (Dark)": {
270 "Blue Component": 0.6313725490196078,
271 "Color Space": "sRGB",
272 "Green Component": 0.6313725490196078,
273 "Red Component": 0.5764705882352941,
274 },
275 "Ansi 14 Color (Light)": {
276 "Blue Component": 0.6313725490196078,
277 "Color Space": "sRGB",
278 "Green Component": 0.6313725490196078,
279 "Red Component": 0.5764705882352941,
280 },
281 "Ansi 15 Color": {
282 "Alpha Component": 1.0,
283 "Blue Component": 0.8977216482162476,
284 "Color Space": "P3",
285 "Green Component": 0.9656357169151306,
286 "Red Component": 0.9873548150062561,
287 },
288 "Ansi 15 Color (Dark)": {
289 "Blue Component": 0.8901960784313725,
290 "Color Space": "sRGB",
291 "Green Component": 0.9647058823529412,
292 "Red Component": 0.9921568627450981,
293 },
294 "Ansi 15 Color (Light)": {
295 "Blue Component": 0.8901960784313725,
296 "Color Space": "sRGB",
297 "Green Component": 0.9647058823529412,
298 "Red Component": 0.9921568627450981,
299 },
300 "Ansi 8 Color": {
301 "Alpha Component": 1.0,
302 "Blue Component": 0.2070317268371582,
303 "Color Space": "P3",
304 "Green Component": 0.16550064086914062,
305 "Red Component": 0.053836725652217865,
306 },
307 "Ansi 8 Color (Dark)": {
308 "Blue Component": 0.21176470588235294,
309 "Color Space": "sRGB",
310 "Green Component": 0.16862745098039217,
311 "Red Component": 0.0,
312 },
313 "Ansi 8 Color (Light)": {
314 "Blue Component": 0.21176470588235294,
315 "Color Space": "sRGB",
316 "Green Component": 0.16862745098039217,
317 "Red Component": 0.0,
318 },
319 "Ansi 9 Color": {
320 "Alpha Component": 1.0,
321 "Blue Component": 0.1624806821346283,
322 "Color Space": "P3",
323 "Green Component": 0.3279757499694824,
324 "Red Component": 0.7377541661262512,
325 },
326 "Ansi 9 Color (Dark)": {
327 "Blue Component": 0.08627450980392157,
328 "Color Space": "sRGB",
329 "Green Component": 0.29411764705882354,
330 "Red Component": 0.796078431372549,
331 },
332 "Ansi 9 Color (Light)": {
333 "Blue Component": 0.08627450980392157,
334 "Color Space": "sRGB",
335 "Green Component": 0.29411764705882354,
336 "Red Component": 0.796078431372549,
337 },
338 "Badge Color": {
339 "Alpha Component": 0.5,
340 "Blue Component": 0.14500156044960022,
341 "Color Space": "P3",
342 "Green Component": 0.25274839997291565,
343 "Red Component": 0.9191656708717346,
344 },
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,
351 },
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,
358 },
359 "Bold Color": {
360 "Alpha Component": 1.0,
361 "Blue Component": 0.6304863095283508,
362 "Color Space": "P3",
363 "Green Component": 0.6296467781066895,
364 "Red Component": 0.5867035984992981,
365 },
366 "Bold Color (Dark)": {
367 "Blue Component": 0.6313725490196078,
368 "Color Space": "sRGB",
369 "Green Component": 0.6313725490196078,
370 "Red Component": 0.5764705882352941,
371 },
372 "Bold Color (Light)": {
373 "Blue Component": 0.4588235294117647,
374 "Color Space": "sRGB",
375 "Green Component": 0.43137254901960786,
376 "Red Component": 0.34509803921568627,
377 },
378 "Cursor Color": {
379 "Alpha Component": 1.0,
380 "Blue Component": 0.5864985585212708,
381 "Color Space": "P3",
382 "Green Component": 0.5783330798149109,
383 "Red Component": 0.5263533592224121,
384 },
385 "Cursor Color (Dark)": {
386 "Blue Component": 0.5882352941176471,
387 "Color Space": "sRGB",
388 "Green Component": 0.5803921568627451,
389 "Red Component": 0.5137254901960784,
390 },
391 "Cursor Guide Color": {
392 "Alpha Component": 0.25,
393 "Blue Component": 0.9907825589179993,
394 "Color Space": "P3",
395 "Green Component": 0.9204942584037781,
396 "Red Component": 0.7486235499382019,
397 },
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,
404 },
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,
411 },
412 "Cursor Text Color": {
413 "Alpha Component": 1.0,
414 "Blue Component": 0.2535606920719147,
415 "Color Space": "P3",
416 "Green Component": 0.20825165510177612,
417 "Red Component": 0.08827308565378189,
418 },
419 "Cursor Text Color (Dark)": {
420 "Blue Component": 0.25882352941176473,
421 "Color Space": "sRGB",
422 "Green Component": 0.21176470588235294,
423 "Red Component": 0.027450980392156862,
424 },
425 "Match Background Color": {
426 "Alpha Component": 1.0,
427 "Blue Component": 0.32116127014160156,
428 "Color Space": "P3",
429 "Green Component": 0.9860088229179382,
430 "Red Component": 0.9969714283943176,
431 },
432 "Match Background Color (Dark)": {
433 "Alpha Component": 1.0,
434 "Blue Component": 0.32116127014160156,
435 "Color Space": "P3",
436 "Green Component": 0.9860088229179382,
437 "Red Component": 0.9969714283943176,
438 },
439 "Match Background Color (Light)": {
440 "Alpha Component": 1.0,
441 "Blue Component": 0.32116127014160156,
442 "Color Space": "P3",
443 "Green Component": 0.9860088229179382,
444 "Red Component": 0.9969714283943176,
445 },
446 "Selected Text Color": {
447 "Alpha Component": 1.0,
448 "Blue Component": 0.6304863095283508,
449 "Color Space": "P3",
450 "Green Component": 0.6296467781066895,
451 "Red Component": 0.5867035984992981,
452 },
453 "Selected Text Color (Dark)": {
454 "Blue Component": 0.6313725490196078,
455 "Color Space": "sRGB",
456 "Green Component": 0.6313725490196078,
457 "Red Component": 0.5764705882352941,
458 },
459 "Selected Text Color (Light)": {
460 "Blue Component": 0.4588235294117647,
461 "Color Space": "sRGB",
462 "Green Component": 0.43137254901960786,
463 "Red Component": 0.34509803921568627,
464 },
465 "Selection Color": {
466 "Alpha Component": 1.0,
467 "Blue Component": 0.2535606920719147,
468 "Color Space": "P3",
469 "Green Component": 0.20825165510177612,
470 "Red Component": 0.08827308565378189,
471 },
472 "Selection Color (Dark)": {
473 "Blue Component": 0.25882352941176473,
474 "Color Space": "sRGB",
475 "Green Component": 0.21176470588235294,
476 "Red Component": 0.027450980392156862,
477 },
478 }
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)
493 )
494 (out_dir / "TextMate_dark.tmTheme").write_text(
495 generate_textmate_theme(colours=palette["dark"], palette_id=palette_id)
496 )
497 (out_dir / "alexwlchan.itermcolors").write_bytes(generate_iterm2_theme(palette))