415 lines
11 KiB
Bash
415 lines
11 KiB
Bash
#!/usr/bin/env bash
|
|
# PS1 module with Nerd Font fallback + disable switch
|
|
# Designed for bash 3.2+ (macOS) and bash 4/5 (Linux)
|
|
# Load this in interactive shells.
|
|
|
|
case "$-" in
|
|
*i*) ;;
|
|
*) return 0 ;;
|
|
esac
|
|
|
|
# Disable switch (per-user/per-shell)
|
|
# 1) env: DISABLE_GLOBAL_PS1=1
|
|
# 2) file: ~/.config/ps1/disable
|
|
if [[ "${DISABLE_GLOBAL_PS1:-0}" == "1" ]]; then
|
|
return 0
|
|
fi
|
|
if [[ -f "$HOME/.config/ps1/disable" ]]; then
|
|
return 0
|
|
fi
|
|
|
|
# Force NF separators globally (rounded caps) unless ASCII is forced.
|
|
export PS1_FORCE_NF=1
|
|
|
|
# Nerd Font detection (best-effort) + overrides
|
|
# PS1_FORCE_ASCII=1 -> always fallback separators
|
|
# PS1_FORCE_NF=1 -> always Nerd Font separators
|
|
_ps1_has_nf() {
|
|
if [[ "${PS1_FORCE_ASCII:-0}" == "1" ]]; then return 1; fi
|
|
if [[ "${PS1_FORCE_NF:-0}" == "1" ]]; then return 0; fi
|
|
|
|
# Linux/WSL: fontconfig often available
|
|
if command -v fc-list >/dev/null 2>&1; then
|
|
fc-list 2>/dev/null | grep -qi "Nerd Font" && return 0
|
|
fi
|
|
|
|
# macOS: fontconfig often not present; default to fallback unless forced
|
|
return 1
|
|
}
|
|
|
|
# Season selection (dynamic or static)
|
|
# PS1_SEASON_MODE=dynamic|static
|
|
# PS1_SEASON= winter|spring|summer|autumn
|
|
# Also reads config from /etc/ps1-season or ~/.config/ps1/season
|
|
_ps1_season_config() {
|
|
local cfg=""
|
|
if [[ -r /etc/ps1-season ]]; then
|
|
cfg="/etc/ps1-season"
|
|
elif [[ -r "$HOME/.config/ps1/season" ]]; then
|
|
cfg="$HOME/.config/ps1/season"
|
|
fi
|
|
|
|
if [[ -n "$cfg" ]]; then
|
|
while IFS='=' read -r k v; do
|
|
k="${k//[[:space:]]/}"
|
|
v="${v//[[:space:]]/}"
|
|
case "$k" in
|
|
PS1_SEASON_MODE) export PS1_SEASON_MODE="$v" ;;
|
|
PS1_SEASON) export PS1_SEASON="$v" ;;
|
|
esac
|
|
done < "$cfg"
|
|
fi
|
|
}
|
|
|
|
_ps1_season_dynamic() {
|
|
local mm dd m d
|
|
mm=$(TZ=Europe/Oslo date +%m); dd=$(TZ=Europe/Oslo date +%d)
|
|
m=$((10#$mm)); d=$((10#$dd))
|
|
|
|
if (( m >= 3 && m <= 5 )); then
|
|
echo "spring"
|
|
elif (( m >= 6 && m <= 8 )); then
|
|
echo "summer"
|
|
elif (( m == 9 || m == 10 || (m == 11 && d < 15) )); then
|
|
echo "autumn"
|
|
else
|
|
echo "winter"
|
|
fi
|
|
}
|
|
|
|
_ps1_season() {
|
|
_ps1_season_config
|
|
local mode="${PS1_SEASON_MODE:-dynamic}"
|
|
local season="${PS1_SEASON:-}"
|
|
|
|
if [[ "$mode" == "static" && -n "$season" ]]; then
|
|
echo "$season"
|
|
else
|
|
_ps1_season_dynamic
|
|
fi
|
|
}
|
|
|
|
# Style selection (aurora/neon/forge/circuit/mono)
|
|
# PS1_STYLE=aurora|neon|forge|circuit|mono
|
|
# PS1_STYLE_PACK=standard|contest
|
|
# PS1_CONTEST_LAYOUT=day-time-user|user-time|time-user|day-time
|
|
# Also reads config from /etc/ps1-style or ~/.config/ps1/style
|
|
_ps1_style_config() {
|
|
local cfg=""
|
|
if [[ -r /etc/ps1-style ]]; then
|
|
cfg="/etc/ps1-style"
|
|
elif [[ -r "$HOME/.config/ps1/style" ]]; then
|
|
cfg="$HOME/.config/ps1/style"
|
|
fi
|
|
|
|
if [[ -n "$cfg" ]]; then
|
|
while IFS='=' read -r k v; do
|
|
k="${k//[[:space:]]/}"
|
|
v="${v//[[:space:]]/}"
|
|
case "$k" in
|
|
PS1_STYLE) export PS1_STYLE="$v" ;;
|
|
PS1_STYLE_PACK) export PS1_STYLE_PACK="$v" ;;
|
|
PS1_CONTEST_LAYOUT) export PS1_CONTEST_LAYOUT="$v" ;;
|
|
esac
|
|
done < "$cfg"
|
|
fi
|
|
}
|
|
|
|
_ps1_style() {
|
|
_ps1_style_config
|
|
local style="${PS1_STYLE:-aurora}"
|
|
echo "$style"
|
|
}
|
|
|
|
_ps1_style_pack() {
|
|
_ps1_style_config
|
|
local pack="${PS1_STYLE_PACK:-standard}"
|
|
echo "$pack"
|
|
}
|
|
|
|
_ps1_contest_layout() {
|
|
_ps1_style_config
|
|
local layout="${PS1_CONTEST_LAYOUT:-day-time-user}"
|
|
echo "$layout"
|
|
}
|
|
|
|
# Time-based emoji with seasonal accents (Europe/Oslo)
|
|
_ps1_pick_icon() {
|
|
local list_name="$1"
|
|
local season="$2"
|
|
local day
|
|
local -a list
|
|
local idx
|
|
local offset=0
|
|
|
|
day=$(TZ=Europe/Oslo date +%j)
|
|
case "$season" in
|
|
spring) offset=11 ;;
|
|
summer) offset=23 ;;
|
|
autumn) offset=37 ;;
|
|
*) offset=0 ;;
|
|
esac
|
|
|
|
eval "list=(\"\${${list_name}[@]}\")"
|
|
if ((${#list[@]} == 0)); then
|
|
echo ""
|
|
return
|
|
fi
|
|
|
|
idx=$(( (10#$day + offset) % ${#list[@]} ))
|
|
echo "${list[$idx]}"
|
|
}
|
|
|
|
_ps1_symbol() {
|
|
local hh mm h m
|
|
hh=$(TZ=Europe/Oslo date +%H); mm=$(TZ=Europe/Oslo date +%M)
|
|
h=$((10#$hh)); m=$((10#$mm))
|
|
|
|
local season="${__PS1_SEASON:-winter}"
|
|
local pack="${__PS1_STYLE_PACK:-standard}"
|
|
local rot_season="${season}"
|
|
local sunrise coffee work evening night
|
|
local -a sunrise_list work_list
|
|
|
|
if [[ "$pack" == "contest" ]]; then
|
|
rot_season="winter"
|
|
sunrise_list=( "⚡️" "🛰️" "🧬" "🧠" )
|
|
work_list=( "🛠️" "💾" "🧪" "🧩" )
|
|
coffee="☕"; evening="🎛️"; night="🕶️"
|
|
else
|
|
case "$season" in
|
|
spring)
|
|
sunrise_list=( "🌷" "🌱" "🐣" "🌤" )
|
|
work_list=( "🌿" "🪴" "🐝" "🌼" )
|
|
coffee="☕"; evening="🌤"; night="🌙"
|
|
;;
|
|
summer)
|
|
sunrise_list=( "🌞" "🏖️" "🌅" "🌤" )
|
|
work_list=( "🏄" "🏖️" "🚤" "🌴" )
|
|
coffee="🧃"; evening="🌇"; night="🌙"
|
|
;;
|
|
autumn)
|
|
sunrise_list=( "🍁" "🍂" "🌫️" "🌦️" )
|
|
work_list=( "🍂" "🍄" "🎃" "🪵" )
|
|
coffee="☕"; evening="🌆"; night="🌙"
|
|
;;
|
|
*)
|
|
sunrise_list=( "🏔️" "🌨️" "❄️" "🌌" )
|
|
work_list=( "🎿" "⛷️" "🏂" "🧊" )
|
|
coffee="☕"; evening="🌆"; night="🌙"
|
|
;;
|
|
esac
|
|
fi
|
|
|
|
sunrise="$(_ps1_pick_icon sunrise_list "$rot_season")"
|
|
work="$(_ps1_pick_icon work_list "$rot_season")"
|
|
|
|
if (( h >= 5 && h <= 8 )); then echo "$sunrise"
|
|
elif (( h >= 9 && h <= 10 )); then echo "$coffee"
|
|
elif (( h == 11 && m < 30 )); then echo "🥪"
|
|
elif (( (h == 11 && m >= 30) || (h >= 12 && h <= 15) )); then echo "$work"
|
|
elif (( h == 16 )); then echo "🍲"
|
|
elif (( h >= 17 && h <= 22 )); then echo "$evening"
|
|
else echo "$night"
|
|
fi
|
|
}
|
|
|
|
# Path shortening (keeps /home/user or /Users/user visible)
|
|
# bash 3.2 safe
|
|
_ps1_path() {
|
|
local p="$PWD"
|
|
local parts=()
|
|
IFS='/' read -ra parts <<< "$p"
|
|
|
|
if ((${#parts[@]} < 6)); then
|
|
echo "$p"; return
|
|
fi
|
|
|
|
local n=${#parts[@]}
|
|
echo "/${parts[1]}/${parts[2]}/…/${parts[$((n-2))]}/${parts[$((n-1))]}"
|
|
}
|
|
|
|
# Dynamic vars updated before each prompt
|
|
__PS1_SYM=""
|
|
__PS1_PATH=""
|
|
__PS1_STATUS=0
|
|
__PS1_USE_NF=0
|
|
__PS1_SEASON="winter"
|
|
__PS1_STYLE="aurora"
|
|
__PS1_STYLE_PACK="standard"
|
|
__PS1_CONTEST_LAYOUT="day-time-user"
|
|
|
|
_ps1_set_prompt() {
|
|
local RST="\[\e[0m\]"
|
|
|
|
# Seasonal palettes (Z1 is darker than Z2)
|
|
local Z1_BG Z1_FG Z2_BG Z2_FG PATH_FG FRAME
|
|
if [[ "$__PS1_STYLE_PACK" == "contest" ]]; then
|
|
Z1_BG="\[\e[48;5;24m\]"
|
|
Z1_FG="\[\e[38;5;255m\]"
|
|
Z2_BG="\[\e[48;5;31m\]"
|
|
Z2_FG="\[\e[38;5;255m\]"
|
|
PATH_FG="\[\e[38;5;51m\]"
|
|
FRAME="\[\e[38;5;23m\]"
|
|
else
|
|
case "$__PS1_SEASON" in
|
|
spring)
|
|
Z1_BG="\[\e[48;5;71m\]"
|
|
Z1_FG="\[\e[38;5;255m\]"
|
|
Z2_BG="\[\e[48;5;120m\]"
|
|
Z2_FG="\[\e[38;5;22m\]"
|
|
PATH_FG="\[\e[38;5;22m\]"
|
|
FRAME="\[\e[38;5;65m\]"
|
|
;;
|
|
summer)
|
|
Z1_BG="\[\e[48;5;142m\]"
|
|
Z1_FG="\[\e[38;5;255m\]"
|
|
Z2_BG="\[\e[48;5;214m\]"
|
|
Z2_FG="\[\e[38;5;0m\]"
|
|
PATH_FG="\[\e[38;5;232m\]"
|
|
FRAME="\[\e[38;5;130m\]"
|
|
;;
|
|
autumn)
|
|
Z1_BG="\[\e[48;5;95m\]"
|
|
Z1_FG="\[\e[38;5;255m\]"
|
|
Z2_BG="\[\e[48;5;173m\]"
|
|
Z2_FG="\[\e[38;5;255m\]"
|
|
PATH_FG="\[\e[38;5;223m\]"
|
|
FRAME="\[\e[38;5;95m\]"
|
|
;;
|
|
*)
|
|
# winter (default)
|
|
Z1_BG="\[\e[48;5;61m\]"
|
|
Z1_FG="\[\e[38;5;255m\]"
|
|
Z2_BG="\[\e[48;5;37m\]"
|
|
Z2_FG="\[\e[38;5;255m\]"
|
|
PATH_FG="\[\e[38;5;194m\]"
|
|
FRAME="\[\e[38;5;60m\]"
|
|
;;
|
|
esac
|
|
fi
|
|
|
|
# Status colors
|
|
local OK="\[\e[38;5;76m\]"
|
|
local BAD="\[\e[38;5;203m\]"
|
|
local BOLD="\[\e[1m\]"
|
|
local NOBOLD="\[\e[22m\]"
|
|
|
|
local left right sep
|
|
if [[ "$__PS1_USE_NF" -eq 1 ]]; then
|
|
left=""; right=""; sep=""
|
|
else
|
|
left="["; right="]"; sep="▶"
|
|
fi
|
|
|
|
local prompt_sym
|
|
if [[ "$__PS1_STATUS" -eq 0 ]]; then
|
|
prompt_sym="${OK}${BOLD}➜${NOBOLD}${RST}"
|
|
else
|
|
prompt_sym="${BAD}${BOLD}➜${NOBOLD}${RST}"
|
|
fi
|
|
|
|
local style="${__PS1_STYLE:-aurora}"
|
|
local L1="\\d \\A \\u"
|
|
local L_HOST="@\\h"
|
|
local PREFIX=""
|
|
local HOST_PART="${L_HOST}"
|
|
local PATH_PART="${__PS1_PATH}"
|
|
local PATH_SEP=" "
|
|
if [[ "$__PS1_STYLE_PACK" == "contest" ]]; then
|
|
local layout="${__PS1_CONTEST_LAYOUT:-day-time-user}"
|
|
case "$layout" in
|
|
user-time) L1="\\u \\A" ;;
|
|
time-user) L1="\\A \\u" ;;
|
|
day-time) L1="\\d \\A" ;;
|
|
*) L1="\\d \\A \\u" ;;
|
|
esac
|
|
HOST_PART="@\\h - ${__PS1_PATH}"
|
|
PATH_PART=""
|
|
PATH_SEP=""
|
|
PREFIX="\n"
|
|
fi
|
|
|
|
case "$style" in
|
|
neon)
|
|
local N1_BG="\[\e[48;5;201m\]"
|
|
local N1_FG="\[\e[38;5;255m\]"
|
|
local N2_BG="\[\e[48;5;45m\]"
|
|
local N2_FG="\[\e[38;5;16m\]"
|
|
local N_PATH_FG="\[\e[38;5;16m\]"
|
|
local N_FRAME="\[\e[38;5;199m\]"
|
|
PS1="\
|
|
${PREFIX}${N_FRAME}╭─${RST}\
|
|
${N1_BG}${N1_FG}${left} ${BOLD}\\u${NOBOLD} \\d \\A ${RST}${N1_BG}${N2_BG}${N2_FG}${sep}${RST}\
|
|
${N2_BG}${N2_FG} ${HOST_PART}${PATH_SEP}${N_PATH_FG}${PATH_PART}${RST}${N2_BG}${N2_FG}${right}${RST}\
|
|
\n${N_FRAME}╰── ${RST}${prompt_sym} ${__PS1_SYM} "
|
|
;;
|
|
forge)
|
|
local F1_BG="\[\e[48;5;94m\]"
|
|
local F1_FG="\[\e[38;5;230m\]"
|
|
local F2_BG="\[\e[48;5;166m\]"
|
|
local F2_FG="\[\e[38;5;231m\]"
|
|
local F_PATH_FG="\[\e[38;5;224m\]"
|
|
local F_FRAME="\[\e[38;5;130m\]"
|
|
PS1="\
|
|
${PREFIX}${F_FRAME}╭─${RST}\
|
|
${F1_BG}${F1_FG}${left} ${BOLD}${L1}${NOBOLD} ${RST}${F1_BG}${F2_BG}${F2_FG}${sep}${RST}\
|
|
${F2_BG}${F2_FG} ${HOST_PART}${PATH_SEP}${F_PATH_FG}${PATH_PART}${RST}${F2_BG}${F2_FG}${right}${RST}\
|
|
\n${F_FRAME}╰── ${RST}${prompt_sym} ${__PS1_SYM} "
|
|
;;
|
|
circuit)
|
|
local C_FG="\[\e[38;5;46m\]"
|
|
local C_DIM="\[\e[38;5;22m\]"
|
|
local C_PATH="\[\e[38;5;120m\]"
|
|
PS1="\
|
|
${PREFIX}${C_DIM}┏━${RST}${C_FG}[${BOLD}\\u${NOBOLD} ${RST}${C_FG}\\A${RST}${C_DIM}]${RST}\
|
|
${C_DIM}━${RST}${C_FG}${HOST_PART}${RST}${PATH_SEP}${C_PATH}${PATH_PART}${RST}\
|
|
\n${C_DIM}┗━${RST}${prompt_sym} ${__PS1_SYM} "
|
|
;;
|
|
mono)
|
|
local M_FG="\[\e[38;5;250m\]"
|
|
local M_DIM="\[\e[38;5;240m\]"
|
|
PS1="\
|
|
${PREFIX}${M_FG}⟦${L1}⟧ ${M_DIM}${HOST_PART}${PATH_SEP}${M_FG}${PATH_PART}${RST}\
|
|
\n${M_DIM}└─${RST}${prompt_sym} ${__PS1_SYM} "
|
|
;;
|
|
*)
|
|
# aurora (default)
|
|
PS1="\
|
|
${PREFIX}${FRAME}╭─${RST}\
|
|
${Z1_BG}${Z1_FG}${left} ${L1} ${RST}${Z1_BG}${Z2_BG}${Z2_FG}${sep}${RST}\
|
|
${Z2_BG}${Z2_FG} ${HOST_PART}${PATH_SEP}${PATH_FG}${PATH_PART}${RST}${Z2_BG}${Z2_FG}${right}${RST}\
|
|
\n${FRAME}╰── ${RST}${prompt_sym} ${__PS1_SYM} "
|
|
;;
|
|
esac
|
|
}
|
|
|
|
_ps1_update() {
|
|
__PS1_STATUS=$?
|
|
__PS1_SEASON="$(_ps1_season)"
|
|
__PS1_STYLE_PACK="$(_ps1_style_pack)"
|
|
__PS1_CONTEST_LAYOUT="$(_ps1_contest_layout)"
|
|
__PS1_STYLE="$(_ps1_style)"
|
|
__PS1_SYM="$(_ps1_symbol)"
|
|
__PS1_PATH="$(_ps1_path)"
|
|
if _ps1_has_nf; then __PS1_USE_NF=1; else __PS1_USE_NF=0; fi
|
|
_ps1_set_prompt
|
|
}
|
|
|
|
ps1_on() {
|
|
if [[ "${PROMPT_COMMAND:-}" != *"_ps1_update"* ]]; then
|
|
if [[ -n "${PROMPT_COMMAND:-}" ]]; then
|
|
PROMPT_COMMAND="${PROMPT_COMMAND}; _ps1_update"
|
|
else
|
|
PROMPT_COMMAND="_ps1_update"
|
|
fi
|
|
fi
|
|
|
|
_ps1_set_prompt
|
|
}
|
|
|
|
ps1_off() { :; }
|
|
|
|
# Enable by default when loaded (global standard)
|
|
ps1_on
|