themable prompt and some proposed themes

This commit is contained in:
fatalerrors
2026-04-15 13:35:33 +02:00
parent bb9bbfda16
commit 85f02f4498
11 changed files with 759 additions and 18 deletions

View File

@@ -34,6 +34,134 @@
# * OF SUCH DAMAGE.
# ------------------------------------------------------------------------------
# ------------------------------------------------------------------------------
# Parse a prompt theme file safely — it is NEVER sourced or executed.
# Two categories of keys are accepted:
# PROMPT_COLOR_* — prompt slot colours (TIME_FG, BAR_BG, …)
# Standard colour variables from disp.sh (Blue, On_IBlack, …) — allows a
# theme to redefine the palette used everywhere in the shell session.
# Allowed value forms:
# $ColorName or ${ColorName} — colour variable from disp.sh (resolved by
# indirection via ${!varname})
# \e[...m or \033[...m — raw ANSI escape literal (single block)
# Any other key or value is rejected with a warning.
# Usage: load_theme <theme_name_or_path> [theme_dir]
# theme_name_or_path : bare name (e.g. "dark") or an explicit path.
# theme_dir : directory to search for bare names; defaults to
# $MYPATH/profile.d/themes. Overridable via
# PROMPT_THEME_DIR.
load_theme()
{
local theme_name="$1"
local theme_dir="${2:-${PROMPT_THEME_DIR:-$MYPATH/profile.d/themes}}"
local theme_file=""
[[ -z "$theme_name" ]] && return 0
if [[ "$theme_name" == /* || "$theme_name" == */* ]]; then
theme_file="$theme_name"
else
theme_file="$theme_dir/${theme_name}.theme"
fi
if [[ ! -f "$theme_file" || ! -r "$theme_file" ]]; then
printf "[ Warning ] load_theme: theme file not found: %s\n" "$theme_file" >&2
return 1
fi
# ---- Key whitelist: prompt slots ----------------------------------------
local -A _lth_allowed=(
[PROMPT_COLOR_TIME_FG]=1 [PROMPT_COLOR_TIME_BG]=1
[PROMPT_COLOR_BAR_BG]=1
[PROMPT_COLOR_OK_FG]=1 [PROMPT_COLOR_OK_MARK]=1
[PROMPT_COLOR_ERR_BG]=1 [PROMPT_COLOR_ERR_FG]=1 [PROMPT_COLOR_ERR_MARK]=1
[PROMPT_COLOR_ROOT_FG]=1 [PROMPT_COLOR_USER_FG]=1
[PROMPT_COLOR_DIR_FG]=1
)
# ---- Colour variable names exported by disp.sh --------------------------
local _lth_color_re
_lth_color_re='Black|Red|Green|Yellow|Blue|Purple|Cyan|White'
_lth_color_re+='|BBlack|BRed|BGreen|BYellow|BBlue|BPurple|BCyan|BWhite'
_lth_color_re+='|UBlack|URed|UGreen|UYellow|UBlue|UPurple|UCyan|UWhite'
_lth_color_re+='|On_Black|On_Red|On_Green|On_Yellow|On_Blue|On_Purple|On_Cyan|On_White'
_lth_color_re+='|IBlack|IRed|IGreen|IYellow|IBlue|IPurple|ICyan|IWhite'
_lth_color_re+='|BIBlack|BIRed|BIGreen|BIYellow|BIBlue|BIPurple|BICyan|BIWhite'
_lth_color_re+='|On_IBlack|On_IRed|On_IGreen|On_IYellow|On_IBlue|On_IPurple|On_ICyan|On_IWhite'
_lth_color_re+='|DEFAULTFG|DEFAULTBG|DEFAULTCOL|RESETCOL'
# ---- Key whitelist: standard colour vars (same list as above) -----------
local _lth_cn
for _lth_cn in \
Black Red Green Yellow Blue Purple Cyan White \
BBlack BRed BGreen BYellow BBlue BPurple BCyan BWhite \
UBlack URed UGreen UYellow UBlue UPurple UCyan UWhite \
On_Black On_Red On_Green On_Yellow On_Blue On_Purple On_Cyan On_White \
IBlack IRed IGreen IYellow IBlue IPurple ICyan IWhite \
BIBlack BIRed BIGreen BIYellow BIBlue BIPurple BICyan BIWhite \
On_IBlack On_IRed On_IGreen On_IYellow On_IBlue On_IPurple On_ICyan On_IWhite \
DEFAULTFG DEFAULTBG DEFAULTCOL RESETCOL; do
_lth_allowed[$_lth_cn]=1
done
unset _lth_cn
# ERE: safe colour reference $Name or ${Name}
local _lth_ref_re='^\$\{?('"$_lth_color_re"')\}?$'
# ERE: raw ANSI escape literal \e[...m or \033[...m
local _lth_ansi_re='^(\\e|\\033)\[[0-9;]*m$'
# ---- Line parser ---------------------------------------------------------
local _lth_line _lth_key _lth_value _lth_varname _lth_lineno=0
while IFS= read -r _lth_line || [[ -n "$_lth_line" ]]; do
((_lth_lineno++))
_lth_line="${_lth_line%$'\r'}" # strip CR
_lth_line="${_lth_line#"${_lth_line%%[![:space:]]*}"}" # ltrim
_lth_line="${_lth_line%"${_lth_line##*[![:space:]]}"}" # rtrim
[[ -z "$_lth_line" || "$_lth_line" == '#'* ]] && continue # blank/comment
[[ "$_lth_line" == 'export '* ]] && _lth_line="${_lth_line#export }" # strip prefix
if [[ "$_lth_line" != *=* ]]; then
printf "[ Warning ] load_theme: %s:%d: not a key=value pair, ignoring.\n" \
"$theme_file" "$_lth_lineno" >&2
continue
fi
_lth_key="${_lth_line%%=*}"
_lth_value="${_lth_line#*=}"
_lth_key="${_lth_key#"${_lth_key%%[![:space:]]*}"}"
_lth_key="${_lth_key%"${_lth_key##*[![:space:]]}"}" # trim key
if [[ -z "${_lth_allowed[$_lth_key]+x}" ]]; then
printf "[ Warning ] load_theme: %s:%d: key '%s' is not allowed, ignoring.\n" \
"$theme_file" "$_lth_lineno" "$_lth_key" >&2
continue
fi
# Strip surrounding quotes
_lth_value="${_lth_value#\"}" ; _lth_value="${_lth_value%\"}"
_lth_value="${_lth_value#\'}" ; _lth_value="${_lth_value%\'}"
if [[ "$_lth_value" =~ $_lth_ref_re ]]; then
# Safe colour variable reference — resolve via indirection
_lth_varname="${_lth_value#\$}"
_lth_varname="${_lth_varname#\{}"
_lth_varname="${_lth_varname%\}}"
export "$_lth_key"="${!_lth_varname}"
elif [[ "$_lth_value" =~ $_lth_ansi_re ]]; then
# Raw ANSI escape literal — accept as-is
export "$_lth_key"="$_lth_value"
else
printf "[ Warning ] load_theme: %s:%d: invalid value for '%s', ignoring.\n" \
"$theme_file" "$_lth_lineno" "$_lth_key" >&2
fi
done < "$theme_file"
}
# ------------------------------------------------------------------------------
# ------------------------------------------------------------------------------
# timer_* functions : internal timing function for prompt
# Usage: timer_now
@@ -95,38 +223,68 @@ set_prompt()
local FancyX='\342\234\227'
local Checkmark='\342\234\223'
# Resolve theme/config colours with hardcoded fallbacks
local _time_fg="${PROMPT_COLOR_TIME_FG:-$Blue}"
local _time_bg="${PROMPT_COLOR_TIME_BG:-$On_IBlack}"
local _bar_bg="${PROMPT_COLOR_BAR_BG:-$On_Blue}"
local _ok_fg="${PROMPT_COLOR_OK_FG:-$White}"
local _ok_mark="${PROMPT_COLOR_OK_MARK:-$Green}"
local _err_bg="${PROMPT_COLOR_ERR_BG:-$On_Red}"
local _err_fg="${PROMPT_COLOR_ERR_FG:-$White}"
local _err_mark="${PROMPT_COLOR_ERR_MARK:-$BYellow}"
local _root_fg="${PROMPT_COLOR_ROOT_FG:-$Red}"
local _user_fg="${PROMPT_COLOR_USER_FG:-$Green}"
local _dir_fg="${PROMPT_COLOR_DIR_FG:-$ICyan}"
# Begin with time
PS1="\[\e[s$Blue$OnGrey [ \t ] $On_Blue"
PS1="\[\e[s${_time_fg}${_time_bg} [ \t ] ${_bar_bg}"
# Add a bright white exit status for the last command
# If it was successful, print a green check mark. Otherwise, print
# a red X.
# Add exit status of the last command.
# If it was successful, print a green check mark. Otherwise, print a red X.
if [[ $Last_Command == 0 ]]; then
PS1+="$White$On_Blue [ \$Last_Command "
PS1+="$Green$Checkmark "
PS1+="${_ok_fg}${_bar_bg} [ \$Last_Command "
PS1+="${_ok_mark}${Checkmark} "
else
PS1+="$White$On_Red [ \$Last_Command "
PS1+="$BYellow$FancyX "
PS1+="${_err_fg}${_err_bg} [ \$Last_Command "
PS1+="${_err_mark}${FancyX} "
fi
# Add the ellapsed time and current date
# Add the elapsed time
timer_stop
PS1+="($timer_show)$White ] $On_Blue "
PS1+="($timer_show)${_ok_fg} ] ${_bar_bg} "
# If root, just print the host in red. Otherwise, print the current user
# and host in green.
# If root, print the host in root colour. Otherwise use user colour.
if [[ $EUID -eq 0 ]]; then
PS1+="$Red\\u$Green@\\h"
PS1+="${_root_fg}\\u${_user_fg}@\\h"
else
PS1+="$Green\\u@\\h"
PS1+="${_user_fg}\\u@\\h"
fi
PS1+="\e[K\e[u$DEFAULTCOL\n"
# Print the working directory and prompt marker in blue, and reset
# the text color to the default.
PS1+="$ICyan\\w \\\$$DEFAULTCOL "
# Print the working directory and prompt marker, then reset colour.
PS1+="${_dir_fg}\\w \\\$$DEFAULTCOL "
}
# ------------------------------------------------------------------------------
# ------------------------------------------------------------------------------
# Theme and configuration loading.
# Precedence (lowest → highest):
# 1. Hardcoded fallbacks in set_prompt
# 2. Theme file (PROMPT_THEME key from [prompt] section)
# 3. Individual PROMPT_COLOR_* overrides in [prompt] section
#
# CONF_prompt is already populated by parse_conf (run in profile.sh before
# modules are sourced). We extract PROMPT_THEME and PROMPT_THEME_DIR from the
# raw associative array now so load_theme can run before load_conf "prompt"
# exports remaining keys. That way any PROMPT_COLOR_* value set explicitly in
# [prompt] wins over the same variable set by the theme file.
_pt_theme="${CONF_prompt[PROMPT_THEME]:-}"
_pt_dir="${CONF_prompt[PROMPT_THEME_DIR]:-}"
[[ -n "$_pt_theme" ]] && load_theme "$_pt_theme" ${_pt_dir:+"$_pt_dir"}
unset _pt_theme _pt_dir
load_conf "prompt"
# ------------------------------------------------------------------------------
# EOF