diff options
Diffstat (limited to 'bin/theme-gen')
| -rwxr-xr-x | bin/theme-gen | 223 |
1 files changed, 223 insertions, 0 deletions
diff --git a/bin/theme-gen b/bin/theme-gen new file mode 100755 index 0000000..6000e5e --- /dev/null +++ b/bin/theme-gen @@ -0,0 +1,223 @@ +#!/bin/bash +# Generate tmux, ghostty and (from ghostty-format sources) alacritty color +# files, so bin/theme can use any theme with `theme --set <mode> <name>`. +# +# Sources: the alacritty-theme checkout (~/src/alacritty/alacritty-theme/ +# themes) by name, or any theme file by path -- alacritty-format .toml, or the +# `palette = N=#hex` format Ghostty.app bundles its built-in themes in. +# +# Writes: +# $XDG_CONFIG_HOME/tmux/colors/<name>.conf +# $XDG_CONFIG_HOME/ghostty/colors/<name> +# $XDG_CONFIG_HOME/alacritty/colors/<name>.toml (ghostty-format sources +# only: an alacritty-format +# source is used in place) +# +# Usage: +# theme-gen <name> # theme from the alacritty-theme checkout +# theme-gen /path/to/theme.toml # or a ghostty theme file +# theme-gen -n <slug> <path> # generate as <slug>, not the basename +# theme-gen -f <name> # overwrite existing generated files +# +# Nothing is activated: run `theme --set <mode> <name>` afterwards. + +set -euo pipefail + +: ${HOME:="$(cd ~ && pwd)"} +: ${XDG_CONFIG_HOME:=$HOME/.config} +themes_dir="$HOME/src/alacritty/alacritty-theme/themes" +ghostty_themes_dir="/Applications/Ghostty.app/Contents/Resources/ghostty/themes" + +usage() { + sed -n '2,19p' "$0" | sed 's/^# \{0,1\}//' + exit "${1:-0}" +} + +force=0 +while getopts :fn:h opt; do + case "$opt" in + f) force=1 ;; + n) override_name="$OPTARG" ;; + h) usage 0 ;; + *) usage 1 ;; + esac +done +shift $((OPTIND - 1)) + +if [[ $# -ne 1 ]]; then + usage 1 +fi + +src="$1" +if [[ -f "$src" ]]; then + name="$(basename "$src")" + name="${name%.toml}" +else + name="$src" + src="${themes_dir}/${name}.toml" +fi +if [[ -n "${override_name:-}" ]]; then + name="$override_name" +fi + +if [[ ! -f "$src" ]]; then + echo "ERROR: no theme file at $src" >&2 + echo "Themes available in $themes_dir:" >&2 + ls "$themes_dir" >&2 2>/dev/null || true + echo "Ghostty built-ins live in $ghostty_themes_dir" >&2 + exit 1 +fi + +# Ghostty bundles themes as `palette = N=#hex` plus background/foreground; +# alacritty themes are .toml sections. Parse either into the same +# <section>.<key>=<hex> lines (no leading '#', lowercased) the writers below +# consume. +parse_colors() { + if grep -qE '^[ \t]*palette[ \t]*=' "$src"; then + awk ' + function hexof(val) { + if (match(val, /#[0-9A-Fa-f][0-9A-Fa-f][0-9A-Fa-f][0-9A-Fa-f][0-9A-Fa-f][0-9A-Fa-f]/)) + return tolower(substr(val, RSTART + 1, RLENGTH - 1)) + return "" + } + BEGIN { split("black red green yellow blue magenta cyan white", K, " ") } + /^[ \t]*palette[ \t]*=/ { + line = $0 + sub(/^[ \t]*palette[ \t]*=[ \t]*/, "", line) + split(line, kv, "=") + idx = kv[1] + 0 + hex = tolower(kv[2]); gsub(/[ \t]/, "", hex); sub(/^#/, "", hex) + if (hex !~ /^[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]$/) next + if (idx < 0 || idx > 15) next + print ((idx < 8) ? "normal" : "bright") "." K[(idx % 8) + 1] "=" hex + next + } + /^[ \t]*background[ \t]*=/ { hex = hexof($0); if (hex != "") print "primary.background=" hex; next } + /^[ \t]*foreground[ \t]*=/ { hex = hexof($0); if (hex != "") print "primary.foreground=" hex; next } + ' "$src" + else + awk ' + /^[ \t]*\[/ { + section = $0 + sub(/^[ \t]*\[/, "", section); sub(/\].*$/, "", section) + next + } + /[ \t]*=/ { + sect = section + sub(/^colors\./, "", sect) + if (sect != "primary" && sect != "normal" && sect != "bright") next + eq = index($0, "=") + key = substr($0, 1, eq - 1); gsub(/[ \t]/, "", key) + val = substr($0, eq + 1) + if (match(val, /"[^"]*"|'"'"'[^'"'"']*'"'"'/)) { + v = substr(val, RSTART + 1, RLENGTH - 2) + } else if (match(val, /#[0-9A-Fa-f][0-9A-Fa-f][0-9A-Fa-f][0-9A-Fa-f][0-9A-Fa-f][0-9A-Fa-f]/)) { + v = substr(val, RSTART, RLENGTH) + } else next + gsub(/[ \t]/, "", v) + if (v !~ /^#?[0-9A-Fa-f][0-9A-Fa-f][0-9A-Fa-f][0-9A-Fa-f][0-9A-Fa-f][0-9A-Fa-f]$/) next + sub(/^#/, "", v) + print sect "." key "=" tolower(v) + } + ' "$src" + fi +} + +# Ghostty-format sources have no alacritty file of their own, so theme-gen +# writes one; alacritty-format sources are used in place, so none is written. +ghostty_source=0 +if grep -qE '^[ \t]*palette[ \t]*=' "$src"; then + ghostty_source=1 +fi + +# no associative arrays: /bin/bash on macOS is 3.2. Keys are sanitized into +# variable names like c_primary_background. +c_key() { + printf '%s' "$1" | tr '.' '_' +} +while IFS='=' read -r k v; do + # keys come from the theme file and go through eval: accept only plain + # section.key names and hex values, or a theme file could run commands + [[ "$k" =~ ^[a-z]+\.[a-z_]+$ && "$v" =~ ^[0-9a-f]{6}$ ]] || continue + eval "c_$(c_key "$k")='$v'" +done < <(parse_colors) + +get() { + local var="c_$(c_key "$1")" fallback="${2-}" + if [[ -n "${!var:-}" ]]; then + printf '%s' "${!var}" + elif [[ -n "$fallback" ]]; then + printf '%s' "$fallback" + else + echo "ERROR: theme is missing $1" >&2 + exit 1 + fi +} + +bg="$(get primary.background)" +fg="$(get primary.foreground)" + +tmux_out="$XDG_CONFIG_HOME/tmux/colors/${name}.conf" +ghostty_out="$XDG_CONFIG_HOME/ghostty/colors/${name}" +alacritty_out="$XDG_CONFIG_HOME/alacritty/colors/${name}.toml" +for out in "$tmux_out" "$ghostty_out" $([[ "$ghostty_source" -eq 1 ]] && echo "$alacritty_out"); do + if [[ -e "$out" && "$force" -ne 1 ]]; then + echo "ERROR: $out already exists; use -f to overwrite" >&2 + exit 1 + fi +done + +if [[ "$ghostty_source" -eq 1 ]]; then + { + echo "# ${name} (generated by theme-gen from ${src})" + echo "[colors.primary]" + echo "background = \"#${bg}\"" + echo "foreground = \"#${fg}\"" + echo + echo "[colors.normal]" + for key in black red green yellow blue magenta cyan white; do + echo "${key} = \"#$(get "normal.${key}")\"" + done + echo + echo "[colors.bright]" + for key in black red green yellow blue magenta cyan white; do + echo "${key} = \"#$(get "bright.${key}")\"" + done + } > "$alacritty_out" +fi + +# Ghostty: primary + full 16-color palette. +{ + echo "# ${name} color scheme (generated by theme-gen from ${src})" + echo + echo "background = #${bg}" + echo "foreground = #${fg}" + echo + local_i=0 + for section in normal bright; do + for key in black red green yellow blue magenta cyan white; do + echo "palette = ${local_i}=#$(get "${section}.${key}" "$(get "normal.${key}" "${fg}")")" + local_i=$((local_i + 1)) + done + done +} > "$ghostty_out" + +# tmux: status bar, messages, pane borders. tmux >= 3.2 takes hex directly. +pane_border="$(get bright.black "$(get normal.black "${fg}")")" +pane_active="$(get normal.blue "${fg}")" +mkdir -p "$(dirname "$tmux_out")" +{ + echo "# ${name} (generated by theme-gen from ${src})" + echo "set -g status-style fg=#${fg},bg=default" + echo "set -g message-style fg=#${bg},bg=#${fg},fill=#${fg}" + echo "set -g pane-border-style fg=#${pane_border}" + echo "set -g pane-active-border-style fg=#${pane_active}" + echo "set -g window-status-current-style fg=#${fg}" +} > "$tmux_out" + +echo "wrote $tmux_out" +echo "wrote $ghostty_out" +if [[ "$ghostty_source" -eq 1 ]]; then + echo "wrote $alacritty_out" +fi +echo "activate with: theme --set <mode> ${name}" |
