1#![doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/README.md"))]
2
3use figment::{
4 providers::{Format, Toml, Yaml},
5 Figment,
6};
7use ratatui::style::Color;
8use serde::de;
9use serde::de::Deserializer;
10use serde::{Deserialize, Serialize};
11use serde_with::serde_as;
12use std::path::PathBuf;
13use std::str::FromStr;
14use thiserror::Error;
15
16#[derive(Error, Debug)]
19#[non_exhaustive]
20pub enum Base16PaletteError {
21 #[error("unable to extract data from file")]
29 ExtractionFailed(#[from] figment::Error),
30}
31
32#[serde_as]
52#[derive(Debug, Clone, Deserialize, Serialize)]
53#[serde(rename_all = "snake_case")]
54pub struct Base16Palette {
55 #[serde(skip, alias = "scheme")]
57 pub name: &'static str,
58
59 #[serde(skip)]
61 pub author: &'static str,
62
63 #[serde(skip)]
65 pub slug: &'static str,
66
67 #[serde(deserialize_with = "deserialize_from_str")]
69 pub base00: Color,
70
71 #[serde(deserialize_with = "deserialize_from_str")]
73 pub base01: Color,
74
75 #[serde(deserialize_with = "deserialize_from_str")]
78 pub base02: Color,
79
80 #[serde(deserialize_with = "deserialize_from_str")]
82 pub base03: Color,
83
84 #[serde(deserialize_with = "deserialize_from_str")]
86 pub base04: Color,
87
88 #[serde(deserialize_with = "deserialize_from_str")]
90 pub base05: Color,
91
92 #[serde(deserialize_with = "deserialize_from_str")]
95 pub base06: Color,
96
97 #[serde(deserialize_with = "deserialize_from_str")]
99 pub base07: Color,
100
101 #[serde(deserialize_with = "deserialize_from_str")]
103 pub base08: Color,
104
105 #[serde(deserialize_with = "deserialize_from_str")]
107 pub base09: Color,
108
109 #[serde(deserialize_with = "deserialize_from_str")]
111 pub base0a: Color,
112
113 #[serde(deserialize_with = "deserialize_from_str")]
115 pub base0b: Color,
116
117 #[serde(deserialize_with = "deserialize_from_str")]
119 pub base0c: Color,
120
121 #[serde(deserialize_with = "deserialize_from_str")]
123 pub base0d: Color,
124
125 #[serde(deserialize_with = "deserialize_from_str")]
127 pub base0e: Color,
128
129 #[serde(deserialize_with = "deserialize_from_str")]
131 pub base0f: Color,
132}
133
134impl Default for Base16Palette {
135 fn default() -> Self {
136 Self {
137 name: "Default",
138 author: "Dheepak Krishnamurthy",
139 slug: "ratatui-base16",
140 base00: Color::Indexed(0),
141 base01: Color::Indexed(1),
142 base02: Color::Indexed(2),
143 base03: Color::Indexed(3),
144 base04: Color::Indexed(4),
145 base05: Color::Indexed(5),
146 base06: Color::Indexed(6),
147 base07: Color::Indexed(7),
148 base08: Color::Indexed(8),
149 base09: Color::Indexed(9),
150 base0a: Color::Indexed(10),
151 base0b: Color::Indexed(11),
152 base0c: Color::Indexed(12),
153 base0d: Color::Indexed(13),
154 base0e: Color::Indexed(14),
155 base0f: Color::Indexed(15),
156 }
157 }
158}
159
160impl Base16Palette {
161 pub fn from_yaml(file: impl Into<PathBuf>) -> Result<Self, Base16PaletteError> {
187 Figment::new()
188 .merge(Yaml::file(file.into()))
189 .extract::<Base16Palette>()
190 .map_err(Base16PaletteError::ExtractionFailed)
191 }
192
193 pub fn from_toml(file: impl Into<PathBuf>) -> Result<Self, Base16PaletteError> {
219 Figment::new()
220 .merge(Toml::file(file.into()))
221 .extract::<Base16Palette>()
222 .map_err(Base16PaletteError::ExtractionFailed)
223 }
224}
225
226fn deserialize_from_str<'de, D>(deserializer: D) -> Result<Color, D::Error>
227where
228 D: Deserializer<'de>,
229{
230 let s = String::deserialize(deserializer)?;
231 if s.starts_with('#') {
232 Color::from_str(&s).map_err(de::Error::custom)
233 } else {
234 Color::from_str(&format!("#{s}")).map_err(de::Error::custom)
235 }
236}
237
238macro_rules! palette {
239 (
240 $name:ident,
241 scheme : $scheme:literal,
242 author : $author:literal,
243 slug : $slug:literal,
244 base00 : $base00:literal,
245 base01 : $base01:literal,
246 base02 : $base02:literal,
247 base03 : $base03:literal,
248 base04 : $base04:literal,
249 base05 : $base05:literal,
250 base06 : $base06:literal,
251 base07 : $base07:literal,
252 base08 : $base08:literal,
253 base09 : $base09:literal,
254 base0a : $base0a:literal,
255 base0b : $base0b:literal,
256 base0c : $base0c:literal,
257 base0d : $base0d:literal,
258 base0e : $base0e:literal,
259 base0f : $base0f:literal,
260 ) => {
261 pub const $name: $crate::Base16Palette = $crate::Base16Palette {
262 name: $scheme,
263 author: $author,
264 slug: $slug,
265 base00: ratatui::style::Color::from_u32($base00),
266 base01: ratatui::style::Color::from_u32($base01),
267 base02: ratatui::style::Color::from_u32($base02),
268 base03: ratatui::style::Color::from_u32($base03),
269 base04: ratatui::style::Color::from_u32($base04),
270 base05: ratatui::style::Color::from_u32($base05),
271 base06: ratatui::style::Color::from_u32($base06),
272 base07: ratatui::style::Color::from_u32($base07),
273 base08: ratatui::style::Color::from_u32($base08),
274 base09: ratatui::style::Color::from_u32($base09),
275 base0a: ratatui::style::Color::from_u32($base0a),
276 base0b: ratatui::style::Color::from_u32($base0b),
277 base0c: ratatui::style::Color::from_u32($base0c),
278 base0d: ratatui::style::Color::from_u32($base0d),
279 base0e: ratatui::style::Color::from_u32($base0e),
280 base0f: ratatui::style::Color::from_u32($base0f),
281 };
282 };
283}
284
285palette! {
286 CUPCAKE,
287 scheme: "Cupcake",
288 author: "Chris Kempson (http://chriskempson.com)",
289 slug: "https://github.com/chriskempson/base16-default-schemes/blob/master/cupcake.yaml",
290 base00: 0x00fbf1f2,
291 base01: 0x00f2f1f4,
292 base02: 0x00d8d5dd,
293 base03: 0x00bfb9c6,
294 base04: 0x00a59daf,
295 base05: 0x008b8198,
296 base06: 0x0072677E,
297 base07: 0x00585062,
298 base08: 0x00D57E85,
299 base09: 0x00EBB790,
300 base0a: 0x00DCB16C,
301 base0b: 0x00A3B367,
302 base0c: 0x0069A9A7,
303 base0d: 0x007297B9,
304 base0e: 0x00BB99B4,
305 base0f: 0x00BAA58C,
306}
307
308palette! {
309 DEFAULT_DARK,
310 scheme: "Default Dark",
311 author: "Chris Kempson (http://chriskempson.com)",
312 slug: "https://github.com/chriskempson/base16-default-schemes/blob/master/default-dark.yaml",
313 base00: 0x00181818,
314 base01: 0x00282828,
315 base02: 0x00383838,
316 base03: 0x00585858,
317 base04: 0x00b8b8b8,
318 base05: 0x00d8d8d8,
319 base06: 0x00e8e8e8,
320 base07: 0x00f8f8f8,
321 base08: 0x00ab4642,
322 base09: 0x00dc9656,
323 base0a: 0x00f7ca88,
324 base0b: 0x00a1b56c,
325 base0c: 0x0086c1b9,
326 base0d: 0x007cafc2,
327 base0e: 0x00ba8baf,
328 base0f: 0x00a16946,
329}
330
331palette! {
332 DEFAULT_LIGHT,
333 scheme: "Default Light",
334 author: "Chris Kempson (http://chriskempson.com)",
335 slug: "https://github.com/chriskempson/base16-default-schemes/blob/master/default-light.yaml",
336 base00: 0x00f8f8f8,
337 base01: 0x00e8e8e8,
338 base02: 0x00d8d8d8,
339 base03: 0x00b8b8b8,
340 base04: 0x00585858,
341 base05: 0x00383838,
342 base06: 0x00282828,
343 base07: 0x00181818,
344 base08: 0x00ab4642,
345 base09: 0x00dc9656,
346 base0a: 0x00f7ca88,
347 base0b: 0x00a1b56c,
348 base0c: 0x0086c1b9,
349 base0d: 0x007cafc2,
350 base0e: 0x00ba8baf,
351 base0f: 0x00a16946,
352}
353
354palette! {
355 EIGHTIES,
356 scheme: "Eighties",
357 author: "Chris Kempson (http://chriskempson.com)",
358 slug: "https://github.com/chriskempson/base16-default-schemes/blob/master/eighties.yaml",
359 base00: 0x002d2d2d,
360 base01: 0x00393939,
361 base02: 0x00515151,
362 base03: 0x00747369,
363 base04: 0x00a09f93,
364 base05: 0x00d3d0c8,
365 base06: 0x00e8e6df,
366 base07: 0x00f2f0ec,
367 base08: 0x00f2777a,
368 base09: 0x00f99157,
369 base0a: 0x00ffcc66,
370 base0b: 0x0099cc99,
371 base0c: 0x0066cccc,
372 base0d: 0x006699cc,
373 base0e: 0x00cc99cc,
374 base0f: 0x00d27b53,
375}
376
377palette! {
378 MOCHA,
379 scheme: "Mocha",
380 author: "Chris Kempson (http://chriskempson.com)",
381 slug: "https://github.com/chriskempson/base16-default-schemes/blob/master/mocha.yaml",
382 base00: 0x003b3228,
383 base01: 0x00534636,
384 base02: 0x00645240,
385 base03: 0x007e705a,
386 base04: 0x00b8afad,
387 base05: 0x00d0c8c6,
388 base06: 0x00e9e1dd,
389 base07: 0x00f5eeeb,
390 base08: 0x00cb6077,
391 base09: 0x00d28b71,
392 base0a: 0x00f4bc87,
393 base0b: 0x00beb55b,
394 base0c: 0x007bbda4,
395 base0d: 0x008ab3b5,
396 base0e: 0x00a89bb9,
397 base0f: 0x00bb9584,
398}
399
400palette! {
401 OCEAN,
402 scheme: "Ocean",
403 author: "Chris Kempson (http://chriskempson.com)",
404 slug: "https://github.com/chriskempson/base16-default-schemes/blob/master/ocean.yaml",
405 base00: 0x002b303b,
406 base01: 0x00343d46,
407 base02: 0x004f5b66,
408 base03: 0x0065737e,
409 base04: 0x00a7adba,
410 base05: 0x00c0c5ce,
411 base06: 0x00dfe1e8,
412 base07: 0x00eff1f5,
413 base08: 0x00bf616a,
414 base09: 0x00d08770,
415 base0a: 0x00ebcb8b,
416 base0b: 0x00a3be8c,
417 base0c: 0x0096b5b4,
418 base0d: 0x008fa1b3,
419 base0e: 0x00b48ead,
420 base0f: 0x00ab7967,
421}
422
423palette! {
424 DRACULA,
425 scheme: "Dracula",
426 author: "Mike Barkmin (http://github.com/mikebarkmin) based on Dracula Theme (http://github.com/dracula)",
427 slug: "https://github.com/dracula/base16-dracula-scheme/blob/master/dracula.yaml",
428 base00: 0x00282936,
429 base01: 0x003a3c4e,
430 base02: 0x004d4f68,
431 base03: 0x00626483,
432 base04: 0x0062d6e8,
433 base05: 0x00e9e9f4,
434 base06: 0x00f1f2f8,
435 base07: 0x00f7f7fb,
436 base08: 0x00ea51b2,
437 base09: 0x00b45bcf,
438 base0a: 0x0000f769,
439 base0b: 0x00ebff87,
440 base0c: 0x00a1efe4,
441 base0d: 0x0062d6e8,
442 base0e: 0x00b45bcf,
443 base0f: 0x0000f769,
444}
445
446palette! {
447 GITHUB_LIGHT,
448 scheme: "Github",
449 author: "Defman21",
450 slug: "https://github.com/Defman21/base16-github-scheme/blob/master/github.yaml",
451 base00: 0x00ffffff,
452 base01: 0x00f5f5f5,
453 base02: 0x00c8c8fa,
454 base03: 0x00969896,
455 base04: 0x00e8e8e8,
456 base05: 0x00333333,
457 base06: 0x00ffffff,
458 base07: 0x00ffffff,
459 base08: 0x00ed6a43,
460 base09: 0x000086b3,
461 base0a: 0x00795da3,
462 base0b: 0x00183691,
463 base0c: 0x00183691,
464 base0d: 0x00795da3,
465 base0e: 0x00a71d5d,
466 base0f: 0x00333333,
467}
468
469palette! {
470 ROSE_PINE_DAWN,
471 scheme: "Rosé Pine Dawn",
472 author: "Emilia Dunfelt <edun@dunfelt.se>",
473 slug: "https://github.com/edunfelt/base16-rose-pine-scheme/blob/main/rose-pine-dawn.yaml",
474 base00: 0x00faf4ed,
475 base01: 0x00fffaf3,
476 base02: 0x00f2e9de,
477 base03: 0x009893a5,
478 base04: 0x00797593,
479 base05: 0x00575279,
480 base06: 0x00575279,
481 base07: 0x00cecacd,
482 base08: 0x00b4637a,
483 base09: 0x00ea9d34,
484 base0a: 0x00d7827e,
485 base0b: 0x00286983,
486 base0c: 0x0056949f,
487 base0d: 0x00907aa9,
488 base0e: 0x00ea9d34,
489 base0f: 0x00cecacd,
490}
491
492palette! {
493 ROSE_PINE_MOON,
494 scheme: "Rosé Pine Moon",
495 author: "Emilia Dunfelt <edun@dunfelt.se>",
496 slug: "https://github.com/edunfelt/base16-rose-pine-scheme/blob/main/rose-pine-moon.yaml",
497 base00: 0x00232136,
498 base01: 0x002a273f,
499 base02: 0x00393552,
500 base03: 0x006e6a86,
501 base04: 0x00908caa,
502 base05: 0x00e0def4,
503 base06: 0x00e0def4,
504 base07: 0x0056526e,
505 base08: 0x00eb6f92,
506 base09: 0x00f6c177,
507 base0a: 0x00ea9a97,
508 base0b: 0x003e8fb0,
509 base0c: 0x009ccfd8,
510 base0d: 0x00c4a7e7,
511 base0e: 0x00f6c177,
512 base0f: 0x0056526e,
513}
514
515palette! {
516 ROSE_PINE,
517 scheme: "Rosé Pine",
518 author: "Emilia Dunfelt <edun@dunfelt.se>",
519 slug: "https://github.com/edunfelt/base16-rose-pine-scheme/blob/main/rose-pine.yaml",
520 base00: 0x00191724,
521 base01: 0x001f1d2e,
522 base02: 0x0026233a,
523 base03: 0x006e6a86,
524 base04: 0x00908caa,
525 base05: 0x00e0def4,
526 base06: 0x00e0def4,
527 base07: 0x00524f67,
528 base08: 0x00eb6f92,
529 base09: 0x00f6c177,
530 base0a: 0x00ebbcba,
531 base0b: 0x0031748f,
532 base0c: 0x009ccfd8,
533 base0d: 0x00c4a7e7,
534 base0e: 0x00f6c177,
535 base0f: 0x00524f67,
536}
537
538#[cfg(test)]
539mod tests {
540 use super::*;
541
542 #[test]
543 fn read_from_yaml() {
544 let mut file = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
545 file.push("./.config/dracula.yaml");
546 let _ = Base16Palette::from_yaml(file).unwrap();
547
548 let mut file = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
549 file.push("./.config/github.yaml");
550 let _ = Base16Palette::from_yaml(file).unwrap();
551 }
552}