From b8abe92f91be8099d64b1da41e63611321362f7f Mon Sep 17 00:00:00 2001
From: Daylin Morgan
Date: Wed, 26 Apr 2023 20:03:36 -0500
Subject: [PATCH] feat: implement catppuccin in nim
---
.gitignore | 6 +
README.md | 43 ++---
catppuccin.nimble | 35 ++++
examples/config.nims | 2 +
examples/term.nim | 28 ++++
examples/use_chroma.nim | 11 ++
src/catppuccin.nim | 18 ++
src/catppuccin/chroma.nim | 325 +++++++++++++++++++++++++++++++++++++
src/catppuccin/palette.nim | 112 +++++++++++++
tests/config.nims | 1 +
tests/test1.nim | 9 +
tools/generate.nim | 46 ++++++
12 files changed, 609 insertions(+), 27 deletions(-)
create mode 100644 .gitignore
create mode 100644 catppuccin.nimble
create mode 100644 examples/config.nims
create mode 100644 examples/term.nim
create mode 100644 examples/use_chroma.nim
create mode 100644 src/catppuccin.nim
create mode 100644 src/catppuccin/chroma.nim
create mode 100644 src/catppuccin/palette.nim
create mode 100644 tests/config.nims
create mode 100644 tests/test1.nim
create mode 100644 tools/generate.nim
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..4c07d89
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,6 @@
+/public/
+/tools/palette-porcelain.json
+
+/tests/**
+!/tests/*.nim
+!/tests/*.nims
diff --git a/README.md b/README.md
index 75a5d0a..21e5b93 100644
--- a/README.md
+++ b/README.md
@@ -11,41 +11,30 @@
-
-
-
-
-## Previews
-
-
-๐ป Latte
-
-
-
-๐ชด Frappรฉ
-
-
-
-๐บ Macchiato
-
-
-
-๐ฟ Mocha
-
-
## Usage
-1. Clone this repository locally
-2. Open the app's settings
-3. Select `import theme` and browse to where you cloned Catppuccin
-4. Select it
+```sh
+nimble install https://github.com/catppuccin/nim
+```
+
+The `catppuccin` nim library was designed to interface with [`treeform/chroma`](https://github.com/treeform/chroma), however it is not required for basic usage. Some of the basic color types and transformations have been ported from `chroma`
+
+If you do wish to access `catppuccin` colors alongside `chroma` compile with `-d:inheritChroma` (see `./examples/use_chroma.nim`) to use the color types defined by `chroma`.
+
+### Example
+
+```nim
+import catppuccin
+
+echo mocha.rosewater.color().toHex()
+```
## ๐ FAQ
- Q: **_"Where can I find the doc?"_**\
- A: Run `nim mkDocs`
+ A: Run `nimble docs`
## ๐ Thanks to
diff --git a/catppuccin.nimble b/catppuccin.nimble
new file mode 100644
index 0000000..e9e7899
--- /dev/null
+++ b/catppuccin.nimble
@@ -0,0 +1,35 @@
+import std/[os,strformat]
+# Package
+
+version = "0.1.0"
+author = "Daylin Morgan"
+description = "Soothing pastel theme for nim"
+license = "MIT"
+srcDir = "src"
+
+
+# Dependencies
+
+requires "nim >= 1.6.12"
+
+task gen, "generate src/catppuccin/palette.nim":
+ let paletteJson = "./tools/palette-porcelain.json"
+ let srcUrl = "https://raw.githubusercontent.com/catppuccin/palette/main/palette-porcelain.json"
+ if not fileExists(paletteJson): exec &"wget -O {paletteJson} {srcUrl}"
+ exec "nim r ./tools/generate.nim"
+
+
+task docs, "Deploy doc html + search index to public/ directory":
+ let
+ deployDir = getCurrentDir() / "public"
+ pkgName = "catppuccin"
+ srcFile = getCurrentDir() / "src" / (pkgName & ".nim")
+ gitUrl = "https://github.com/daylinmorgan/catppuccin-nim"
+ selfExec &"doc --index:on --git.url:{gitUrl} --outdir:{deployDir} --project {srcFile}"
+ withDir deployDir:
+ mvFile(pkgName & ".html", "index.html")
+ for file in walkDirRec(".", {pcFile}):
+ # As we renamed the file, we need to rename that in hyperlinks
+ exec(r"sed -i -r 's|$1\.html|index.html|g' $2" % [pkgName, file])
+ # drop 'src/' from titles
+ exec(r"sed -i -r 's/<(.*)>src\//<\1>/' $1" % file)
diff --git a/examples/config.nims b/examples/config.nims
new file mode 100644
index 0000000..07d71fa
--- /dev/null
+++ b/examples/config.nims
@@ -0,0 +1,2 @@
+switch("path", "$projectDir/../src")
+switch("hints", "off")
diff --git a/examples/term.nim b/examples/term.nim
new file mode 100644
index 0000000..76af9c7
--- /dev/null
+++ b/examples/term.nim
@@ -0,0 +1,28 @@
+import std/[strformat]
+import catppuccin
+
+const ansiReset = "\e[0m"
+
+proc ansi(s: string, c: ColorRGB): string =
+ let code = &"\e[48;2;{c.r};{c.g};{c.b}m"
+ result.add(code)
+ result.add(s)
+ result.add(ansiReset)
+
+
+when isMainModule:
+ let flavors = @[
+ ("latte", latte),
+ ("frappe", frappe),
+ ("macchiato", macchiato),
+ ("mocha", mocha)
+ ]
+
+ for (name, flavor) in flavors:
+
+ echo name
+
+ for name, color in flavor.fieldPairs():
+ write(stdout, " ".ansi(color))
+
+ write(stdout, "\n\n")
diff --git a/examples/use_chroma.nim b/examples/use_chroma.nim
new file mode 100644
index 0000000..4c56897
--- /dev/null
+++ b/examples/use_chroma.nim
@@ -0,0 +1,11 @@
+{.define: inheritChroma.}
+
+import std/[strutils]
+
+import catppuccin
+
+
+when isMainModule:
+ echo "Mocha colors as CMYK"
+ for n, c in mocha.fieldPairs():
+ echo alignLeft(n, 9) & " -> " & $c.color().asCmyk()
diff --git a/src/catppuccin.nim b/src/catppuccin.nim
new file mode 100644
index 0000000..03e3924
--- /dev/null
+++ b/src/catppuccin.nim
@@ -0,0 +1,18 @@
+when not defined(inheritChroma):
+ import catppuccin/chroma
+else:
+ import chroma
+
+type
+ Flavor = object
+ rosewater*, flamingo*, pink*, mauve*, red*, maroon*, peach*, yellow*,
+ green*, teal*, sky*, sapphire*, blue*, lavender*, text*, subtext1*,
+ subtext0*, overlay2*,
+ overlay1*, surface2*, surface1*, surface0*, base*, mantle*,
+ crust*: ColorRGB
+
+include catppuccin/palette
+
+
+export mocha, latte, macchiato, frappe
+export chroma
diff --git a/src/catppuccin/chroma.nim b/src/catppuccin/chroma.nim
new file mode 100644
index 0000000..397c7f7
--- /dev/null
+++ b/src/catppuccin/chroma.nim
@@ -0,0 +1,325 @@
+import std/[hashes, math, strutils]
+## standalone types/methods ported from treeform/chroma
+##
+
+# chroma/colortypes ---------------
+type
+ Color* = object
+ ## Main color type, float32 points
+ r*: float32 ## red (0-1)
+ g*: float32 ## green (0-1)
+ b*: float32 ## blue (0-1)
+ a*: float32 ## alpha (0-1, 0 is fully transparent)
+
+ # Color Space: rgb
+ ColorRGB* = object
+ ## Color stored as 3 uint8s
+ r*: uint8 ## Red 0-255
+ g*: uint8 ## Green 0-255
+ b*: uint8 ## Blue 0-255
+
+ # Color Space: rgba
+ ColorRGBA* = object
+ ## Color stored as 4 uint8s
+ r*: uint8 ## Red 0-255
+ g*: uint8 ## Green 0-255
+ b*: uint8 ## Blue 0-255
+ a*: uint8 ## Alpha 0-255
+
+ ColorRGBX* = object
+ ## Premultiplied alpha RGBA color stored as 4 uint8s
+ r*: uint8 ## Red 0-a
+ g*: uint8 ## Green 0-a
+ b*: uint8 ## Blue 0-a
+ a*: uint8 ## Alpha 0-255
+
+ # Color Space: HSL
+ ColorHSL* = object
+ ## HSL attempts to resemble more perceptual color models
+ h*: float32 ## hue 0 to 360
+ s*: float32 ## saturation 0 to 100
+ l*: float32 ## lightness 0 to 100
+
+ SomeColor* = Color|ColorRGB|ColorRGBA|ColorHSL
+
+ InvalidColor* = object of ValueError
+
+proc color*(r, g, b: float32, a: float32 = 1.0): Color {.inline.} =
+ ## Creates from floats like:
+ ## * color(1,0,0) -> red
+ ## * color(0,1,0) -> green
+ ## * color(0,0,1) -> blue
+ ## * color(0,0,0,1) -> opaque black
+ ## * color(0,0,0,0) -> transparent black
+ Color(r: r, g: g, b: b, a: a)
+
+proc rgb*(r, g, b: uint8): ColorRGB {.inline.} =
+ ## Creates from uint8s like:
+ ## * rgba(255,0,0) -> red
+ ## * rgba(0,255,0) -> green
+ ## * rgba(0,0,255) -> blue
+ ColorRGB(r: r, g: g, b: b)
+
+proc rgba*(r, g, b, a: uint8): ColorRGBA {.inline.} =
+ ## Creates from uint8s like:
+ ## * rgba(255,0,0,255) -> red
+ ## * rgba(0,255,0,255) -> green
+ ## * rgba(0,0,255,255) -> blue
+ ## * rgba(0,0,0,255) -> opaque black
+ ## * rgba(0,0,0,0) -> transparent black
+ ColorRGBA(r: r, g: g, b: b, a: a)
+
+proc hsl*(h, s, l: float32): ColorHSL {.inline.} =
+ ColorHSL(h: h, s: s, l: l)
+
+# chroma/colortypes ---------------
+
+# chroma/transformations ----------
+
+proc rgba*(c: ColorRGBX): ColorRGBA {.inline.} =
+ ## Convert a premultiplied alpha RGBA to a straight alpha RGBA.
+ result.r = c.r
+ result.g = c.g
+ result.b = c.b
+ result.a = c.a
+ if result.a != 0 and result.a != 255:
+ let multiplier = round((255 / c.a.float32) * 255).uint32
+ result.r = ((result.r.uint32 * multiplier + 127) div 255).uint8
+ result.g = ((result.g.uint32 * multiplier + 127) div 255).uint8
+ result.b = ((result.b.uint32 * multiplier + 127) div 255).uint8
+
+proc rgb*(c: Color): ColorRGB {.inline.} =
+ ## Convert Color to ColorRGB
+ result.r = round(c.r * 255).uint8
+ result.g = round(c.g * 255).uint8
+ result.b = round(c.b * 255).uint8
+
+proc color*(c: ColorRGB): Color {.inline.} =
+ ## Convert ColorRGB to Color
+ result.r = float32(c.r) / 255
+ result.g = float32(c.g) / 255
+ result.b = float32(c.b) / 255
+ result.a = 1.0
+
+proc rgba*(c: Color): ColorRGBA {.inline.} =
+ ## Convert Color to ColorRGBA
+ result.r = round(c.r * 255).uint8
+ result.g = round(c.g * 255).uint8
+ result.b = round(c.b * 255).uint8
+ result.a = round(c.a * 255).uint8
+
+proc color*(c: ColorRGBA): Color {.inline.} =
+ ## Convert ColorRGBA to Color
+ result.r = float32(c.r) / 255
+ result.g = float32(c.g) / 255
+ result.b = float32(c.b) / 255
+ result.a = float32(c.a) / 255
+
+proc min3(a, b, c: float32): float32 {.inline.} = min(a, min(b, c))
+proc max3(a, b, c: float32): float32 {.inline.} = max(a, max(b, c))
+
+proc hsl*(c: Color): ColorHSL =
+ ## convert Color to ColorHSL
+ let
+ min = min3(c.r, c.g, c.b)
+ max = max3(c.r, c.g, c.b)
+ delta = max - min
+ if max == min:
+ result.h = 0.0
+ elif c.r == max:
+ result.h = (c.g - c.b) / delta
+ elif c.g == max:
+ result.h = 2 + (c.b - c.r) / delta
+ elif c.b == max:
+ result.h = 4 + (c.r - c.g) / delta
+
+ result.h = min(result.h * 60, 360)
+ if result.h < 0:
+ result.h += 360
+
+ result.l = (min + max) / 2
+
+ if max == min:
+ result.s = 0
+ elif result.l <= 0.5:
+ result.s = delta / (max + min)
+ else:
+ result.s = delta / (2 - max - min)
+
+ result.s *= 100
+ result.l *= 100
+
+func fixupColor[T: int | float32](r, g, b: var T): bool =
+ ## performs a fixup of the given r, g, b values and returnes whether
+ ## any of the values was modified.
+ ## This func works on integers or floats. It is only used within the
+ ## conversion of `Color -> ColorHCL` (on integers) and `ColorHCL -> Color`
+ ## (on floats).
+ template fixC(c: untyped): untyped =
+ if c < T(0):
+ c = T(0)
+ result = true
+ when T is int:
+ if c > 255:
+ c = 255
+ result = true
+ else:
+ if c > 1.0:
+ c = 1.0
+ result = true
+ fixC(r)
+ fixC(g)
+ fixC(b)
+
+# overload working on `var Color`. It's `discardable`, because in our usage
+# here we do not really care whether a value was modified.
+func fixupColor(c: var Color): bool {.inline, discardable.} =
+ fixupColor(c.r, c.g, c.b)
+
+proc color*(c: ColorHSL): Color =
+ ## convert ColorHSL to Color
+ let
+ h = c.h / 360
+ s = c.s / 100
+ l = c.l / 100
+ var t1, t2, t3: float32
+ if s == 0.0:
+ return color(l, l, l)
+ if l < 0.5:
+ t2 = l * (1 + s)
+ else:
+ t2 = l + s - l * s
+ t1 = 2 * l - t2
+
+ var rgb: array[3, float32]
+ for i in 0..2:
+ t3 = h + 1.0 / 3.0 * - (float32(i) - 1.0)
+ if t3 < 0:
+ t3 += 1
+ elif t3 > 1:
+ t3 -= 1
+
+ var val: float32
+ if 6 * t3 < 1:
+ val = t1 + (t2 - t1) * 6 * t3
+ elif 2 * t3 < 1:
+ val = t2
+ elif 3 * t3 < 2:
+ val = t1 + (t2 - t1) * (2 / 3 - t3) * 6
+ else:
+ val = t1
+
+ rgb[i] = val
+ result.r = rgb[0]
+ result.g = rgb[1]
+ result.b = rgb[2]
+ result.a = 1.0
+ fixupColor(result)
+ result
+
+proc color*(c: Color): Color {.inline.} =
+ c
+
+proc to*[T: SomeColor](c: SomeColor, toColor: typedesc[T]): T {.inline.} =
+ ## Allows conversion of transformation of a color in any color space into any
+ ## other color space.
+ when type(c) is T:
+ c
+ else:
+ when toColor is Color:
+ c.color
+ elif toColor is ColorRGB:
+ c.color.rgb
+ elif toColor is ColorRGBA:
+ c.color.rgba
+ elif toColor is ColorHSL:
+ c.color.hsl
+
+proc asColor*(c: SomeColor): Color {.inline.} = c.to(Color)
+proc asRgb*(c: SomeColor): ColorRGB {.inline.} = c.to(ColorRGB)
+proc asHsl*(c: SomeColor): ColorHSL {.inline.} = c.to(ColorHSL)
+
+# chroma/transformations ----------
+
+# chroma --------------------------
+
+proc toHex(a: float32): string {.inline.} = toHex(int(a))
+
+proc `$`*(c: Color): string =
+ ## Returns colors as "(r, g, b, a)".
+ "(" & $c.r & ", " & $c.g & ", " & $c.b & ", " & $c.a & ")"
+
+func hash*(c: Color): Hash =
+ ## Hashes a Color - used in tables.
+ hash((c.r, c.g, c.b, c.a))
+
+func hash*(c: ColorRGB): Hash =
+ ## Hashes a ColorRGB - used in tables.
+ hash((c.r, c.g, c.b))
+
+func hash*(c: ColorRGBA): Hash =
+ ## Hashes a ColorRGB - used in tables.
+ hash((c.r, c.g, c.b, c.a))
+
+func hash*(c: ColorHSL): Hash =
+ ## Hashes a ColorHSL - used in tables.
+ hash((c.h, c.s, c.l))
+
+proc toHex*(c: Color): string =
+ ## Formats color as hex (upper case):
+ ## * red -> FF0000
+ ## * blue -> 0000FF
+ ## * white -> FFFFFF
+ template pair(n: float32): string =
+ toHex(n*255)[^2..^1]
+ pair(c.r) & pair(c.g) & pair(c.b)
+
+proc toHexAlpha*(c: Color): string =
+ ## Formats color as hex (upper case):
+ ## * red -> FF0000FF
+ ## * blue -> 0000FFFF
+ ## * white -> FFFFFFFF
+ ## * opaque black -> 000000FF
+ ## * transparent black -> 00000000
+ template pair(n: float32): string =
+ toHex(n*255)[^2..^1]
+ pair(c.r) & pair(c.g) & pair(c.b) & pair(c.a)
+
+proc toHtmlHex*(c: Color): string =
+ ## Formats color as HTML hex (upper case):
+ ## * red -> #FF0000
+ ## * blue -> #0000FF
+ ## * white -> #FFFFFF
+ '#' & c.toHex()
+
+proc toHtmlHexTiny*(c: Color): string =
+ ## Formats color as HTML 3 hex numbers (upper case):
+ ## * red -> #F00
+ ## * blue -> #00F
+ ## * white -> #FFF
+ proc pair(n: float32): string =
+ toHex(n*15)[^1..^1]
+ return '#' & pair(c.r) & pair(c.g) & pair(c.b)
+
+proc toHtmlRgb*(c: Color): string =
+ ## Parses colors in html's rgb format:
+ ## * red -> rgb(255, 0, 0)
+ ## * blue -> rgb(0,0,255)
+ ## * white -> rgb(255,255,255)
+ "rgb(" &
+ $round(c.r * 255).int & ", " &
+ $round(c.g * 255).int & ", " &
+ $round(c.b * 255).int &
+ ")"
+
+proc toHtmlRgba*(c: Color): string =
+ ## Parses colors in html's rgb format:
+ ## * red -> rgb(255, 0, 0)
+ ## * blue -> rgb(0,0,255)
+ ## * white -> rgb(255,255,255)
+ "rgba(" &
+ $round(c.r * 255).int & ", " &
+ $round(c.g * 255).int & ", " &
+ $round(c.b * 255).int & ", " &
+ $c.a &
+ ")"
diff --git a/src/catppuccin/palette.nim b/src/catppuccin/palette.nim
new file mode 100644
index 0000000..c06d9b9
--- /dev/null
+++ b/src/catppuccin/palette.nim
@@ -0,0 +1,112 @@
+# DO NOT EDIT this file is autogenerated by tools/generate.nim!
+
+const
+ latte* = Flavor(
+ rosewater: ColorRGB(r: 220, g: 138, b: 120),
+ flamingo: ColorRGB(r: 221, g: 120, b: 120),
+ pink: ColorRGB(r: 234, g: 118, b: 203),
+ mauve: ColorRGB(r: 136, g: 57, b: 239),
+ red: ColorRGB(r: 210, g: 15, b: 57),
+ maroon: ColorRGB(r: 230, g: 69, b: 83),
+ peach: ColorRGB(r: 254, g: 100, b: 11),
+ yellow: ColorRGB(r: 223, g: 142, b: 29),
+ green: ColorRGB(r: 64, g: 160, b: 43),
+ teal: ColorRGB(r: 23, g: 146, b: 153),
+ sky: ColorRGB(r: 4, g: 165, b: 229),
+ sapphire: ColorRGB(r: 32, g: 159, b: 181),
+ blue: ColorRGB(r: 30, g: 102, b: 245),
+ lavender: ColorRGB(r: 114, g: 135, b: 253),
+ text: ColorRGB(r: 76, g: 79, b: 105),
+ subtext1: ColorRGB(r: 92, g: 95, b: 119),
+ subtext0: ColorRGB(r: 108, g: 111, b: 133),
+ overlay2: ColorRGB(r: 124, g: 127, b: 147),
+ overlay1: ColorRGB(r: 140, g: 143, b: 161),
+ surface2: ColorRGB(r: 172, g: 176, b: 190),
+ surface1: ColorRGB(r: 188, g: 192, b: 204),
+ surface0: ColorRGB(r: 204, g: 208, b: 218),
+ base: ColorRGB(r: 239, g: 241, b: 245),
+ mantle: ColorRGB(r: 230, g: 233, b: 239),
+ crust: ColorRGB(r: 220, g: 224, b: 232)
+ )
+ frappe* = Flavor(
+ rosewater: ColorRGB(r: 242, g: 213, b: 207),
+ flamingo: ColorRGB(r: 238, g: 190, b: 190),
+ pink: ColorRGB(r: 244, g: 184, b: 228),
+ mauve: ColorRGB(r: 202, g: 158, b: 230),
+ red: ColorRGB(r: 231, g: 130, b: 132),
+ maroon: ColorRGB(r: 234, g: 153, b: 156),
+ peach: ColorRGB(r: 239, g: 159, b: 118),
+ yellow: ColorRGB(r: 229, g: 200, b: 144),
+ green: ColorRGB(r: 166, g: 209, b: 137),
+ teal: ColorRGB(r: 129, g: 200, b: 190),
+ sky: ColorRGB(r: 153, g: 209, b: 219),
+ sapphire: ColorRGB(r: 133, g: 193, b: 220),
+ blue: ColorRGB(r: 140, g: 170, b: 238),
+ lavender: ColorRGB(r: 186, g: 187, b: 241),
+ text: ColorRGB(r: 198, g: 208, b: 245),
+ subtext1: ColorRGB(r: 181, g: 191, b: 226),
+ subtext0: ColorRGB(r: 165, g: 173, b: 206),
+ overlay2: ColorRGB(r: 148, g: 156, b: 187),
+ overlay1: ColorRGB(r: 131, g: 139, b: 167),
+ surface2: ColorRGB(r: 98, g: 104, b: 128),
+ surface1: ColorRGB(r: 81, g: 87, b: 109),
+ surface0: ColorRGB(r: 65, g: 69, b: 89),
+ base: ColorRGB(r: 48, g: 52, b: 70),
+ mantle: ColorRGB(r: 41, g: 44, b: 60),
+ crust: ColorRGB(r: 35, g: 38, b: 52)
+ )
+ macchiato* = Flavor(
+ rosewater: ColorRGB(r: 244, g: 219, b: 214),
+ flamingo: ColorRGB(r: 240, g: 198, b: 198),
+ pink: ColorRGB(r: 245, g: 189, b: 230),
+ mauve: ColorRGB(r: 198, g: 160, b: 246),
+ red: ColorRGB(r: 237, g: 135, b: 150),
+ maroon: ColorRGB(r: 238, g: 153, b: 160),
+ peach: ColorRGB(r: 245, g: 169, b: 127),
+ yellow: ColorRGB(r: 238, g: 212, b: 159),
+ green: ColorRGB(r: 166, g: 218, b: 149),
+ teal: ColorRGB(r: 139, g: 213, b: 202),
+ sky: ColorRGB(r: 145, g: 215, b: 227),
+ sapphire: ColorRGB(r: 125, g: 196, b: 228),
+ blue: ColorRGB(r: 138, g: 173, b: 244),
+ lavender: ColorRGB(r: 183, g: 189, b: 248),
+ text: ColorRGB(r: 202, g: 211, b: 245),
+ subtext1: ColorRGB(r: 184, g: 192, b: 224),
+ subtext0: ColorRGB(r: 165, g: 173, b: 203),
+ overlay2: ColorRGB(r: 147, g: 154, b: 183),
+ overlay1: ColorRGB(r: 128, g: 135, b: 162),
+ surface2: ColorRGB(r: 91, g: 96, b: 120),
+ surface1: ColorRGB(r: 73, g: 77, b: 100),
+ surface0: ColorRGB(r: 54, g: 58, b: 79),
+ base: ColorRGB(r: 36, g: 39, b: 58),
+ mantle: ColorRGB(r: 30, g: 32, b: 48),
+ crust: ColorRGB(r: 24, g: 25, b: 38)
+ )
+ mocha* = Flavor(
+ rosewater: ColorRGB(r: 245, g: 224, b: 220),
+ flamingo: ColorRGB(r: 242, g: 205, b: 205),
+ pink: ColorRGB(r: 245, g: 194, b: 231),
+ mauve: ColorRGB(r: 203, g: 166, b: 247),
+ red: ColorRGB(r: 243, g: 139, b: 168),
+ maroon: ColorRGB(r: 235, g: 160, b: 172),
+ peach: ColorRGB(r: 250, g: 179, b: 135),
+ yellow: ColorRGB(r: 249, g: 226, b: 175),
+ green: ColorRGB(r: 166, g: 227, b: 161),
+ teal: ColorRGB(r: 148, g: 226, b: 213),
+ sky: ColorRGB(r: 137, g: 220, b: 235),
+ sapphire: ColorRGB(r: 116, g: 199, b: 236),
+ blue: ColorRGB(r: 137, g: 180, b: 250),
+ lavender: ColorRGB(r: 180, g: 190, b: 254),
+ text: ColorRGB(r: 205, g: 214, b: 244),
+ subtext1: ColorRGB(r: 186, g: 194, b: 222),
+ subtext0: ColorRGB(r: 166, g: 173, b: 200),
+ overlay2: ColorRGB(r: 147, g: 153, b: 178),
+ overlay1: ColorRGB(r: 127, g: 132, b: 156),
+ surface2: ColorRGB(r: 88, g: 91, b: 112),
+ surface1: ColorRGB(r: 69, g: 71, b: 90),
+ surface0: ColorRGB(r: 49, g: 50, b: 68),
+ base: ColorRGB(r: 30, g: 30, b: 46),
+ mantle: ColorRGB(r: 24, g: 24, b: 37),
+ crust: ColorRGB(r: 17, g: 17, b: 27)
+ )
+
diff --git a/tests/config.nims b/tests/config.nims
new file mode 100644
index 0000000..3bb69f8
--- /dev/null
+++ b/tests/config.nims
@@ -0,0 +1 @@
+switch("path", "$projectDir/../src")
\ No newline at end of file
diff --git a/tests/test1.nim b/tests/test1.nim
new file mode 100644
index 0000000..68c7ef7
--- /dev/null
+++ b/tests/test1.nim
@@ -0,0 +1,9 @@
+import unittest
+
+import catppuccin
+
+test "color":
+ check mocha.rosewater == ColorRGB(r: 245, g: 224, b: 220)
+
+test "convert":
+ check mocha.rosewater.color().toHex() == "F5E0DC"
diff --git a/tools/generate.nim b/tools/generate.nim
new file mode 100644
index 0000000..269f41c
--- /dev/null
+++ b/tools/generate.nim
@@ -0,0 +1,46 @@
+import std/[json, strformat, strutils]
+
+
+type
+ Color = object
+ hex: string
+ rgb: array[3, int]
+ hsl: array[3, float]
+ Flavor = object
+ rosewater, flamingo, pink, mauve, red, maroon, peach, yellow, green, teal,
+ sky, sapphire, blue, lavender, text, subtext1, subtext0, overlay2,
+ overlay1, surface2, surface1, surface0, base, mantle, crust: Color
+ Palette = object
+ latte, frappe, macchiato, mocha: Flavor
+
+proc createColor(c: Color, name: string): string =
+ result = &"""
+ {name}: ColorRGB(r: {c.rgb[0]}, g: {c.rgb[1]}, b: {c.rgb[2]})"""
+
+proc createFlavor(f: Flavor, name: string): string =
+ var colorsDef: seq[string]
+ for name, color in f.fieldPairs():
+ colorsDef.add color.createColor(name)
+
+ result = &"""
+ {name}* = Flavor(
+{colorsDef.join(",\n")}
+ )"""
+
+
+when isMainModule:
+ const paletteJsonStr = slurp "./palette-porcelain.json"
+ const doNotEdit = "# DO NOT EDIT this file is autogenerated by tools/generate.nim!"
+
+ let palette = paletteJsonStr.parseJson().to(Palette)
+ var flavors: string
+ for name, flavor in palette.fieldPairs():
+ flavors &= flavor.createFlavor(name) & "\n"
+
+ let catppuccin = &"""
+{doNotEdit}
+
+const
+{flavors}
+"""
+ writeFile("./src/catppuccin/palette.nim", catppuccin)