Convert color script to portable POSIX shell

This commit is contained in:
2026-06-22 19:33:17 -04:00
parent 8db7a5a13e
commit 9ed5dfd09b
+158 -133
View File
@@ -13,54 +13,74 @@
debug=0 debug=0
debug_print()
{
if [ ${debug} -ne 0 ]
then
echo ${*} >&2
fi
}
# This is the named color map. Just add new entries and they'll be supported... # This is the named color map. Just add new entries and they'll be supported...
declare -A map COLOR_TABLE=""
map[white]="FFFFFF"
map[silver]="C0C0C0" map()
map[gray]="808080" {
map[grey]="808080" if [ $# -eq 2 ]
map[black]="000000" then
map[red]="FF0000" COLOR_TABLE="${COLOR_TABLE}:${1}=${2}"
map[maroon]="800000" else
map[yellow]="FFFF00" ___right="${COLOR_TABLE##*:${1}=}"
map[olive]="808000" echo ${___right%%:*}
map[lime]="00FF00" fi
map[green]="008000" }
map[aqua]="00FFFF"
map[teal]="008080" map white "FFFFFF"
map[blue]="0000FF" map silver "C0C0C0"
map[navy]="000080" map gray "808080"
map[fuchsia]="FF00FF" map grey "808080"
map[purple]="800080" map black "000000"
map red "FF0000"
map maroon "800000"
map yellow "FFFF00"
map olive "808000"
map lime "00FF00"
map green "008000"
map aqua "00FFFF"
map teal "008080"
map blue "0000FF"
map navy "000080"
map fuchsia "FF00FF"
map purple "800080"
# Some VGA color names can be promoted: # Some VGA color names can be promoted:
map[brown]="aa5500" map brown "aa5500"
map[cyan]="00aaaa" map cyan "00aaaa"
# The VGA names can be prefixed with "vga-" to explicitly avoid the collisions with HTML names, above... # The VGA names can be prefixed with "vga-" to explicitly avoid the collisions with HTML names, above...
# Based upon wikipedia's article on ANSI. See the table on that page for details. # Based upon wikipedia's article on ANSI. See the table on that page for details.
map[vga-black]="000000" map vga-black "000000"
map[vga-red]="aa0000" map vga-red "aa0000"
map[vga-green]="00aa00" map vga-green "00aa00"
map[vga-brown]="aa5500" map vga-brown "aa5500"
map[vga-yellow]="aa5500" map vga-yellow "aa5500"
map[vga-blue]="0000aa" map vga-blue "0000aa"
map[vga-magenta]="aa00aa" map vga-magenta "aa00aa"
map[vga-cyan]="00aaaa" map vga-cyan "00aaaa"
map[vga-white]="aaaaaa" map vga-white "aaaaaa"
map[vga-brightblack]="555555" map vga-brightblack "555555"
map[vga-grey]="555555" map vga-grey "555555"
map[vga-gray]="555555" map vga-gray "555555"
map[vga-brightred]="ff5555" map vga-brightred "ff5555"
map[vga-brightgreen]="55ff55" map vga-brightgreen "55ff55"
map[vga-brightyellow]="ffff55" map vga-brightyellow "ffff55"
map[vga-brightblue]="5555ff" map vga-brightblue "5555ff"
map[vga-brightmagenta]="ff55ff" map vga-brightmagenta "ff55ff"
map[vga-brightcyan]="55ffff" map vga-brightcyan "55ffff"
map[vga-brightwhite]="ffffff" map vga-brightwhite "ffffff"
enable_greyscale_cheat=0 enable_greyscale_cheat=0
@@ -71,10 +91,13 @@ then
shift 1 shift 1
fi fi
function dump_colors() dump_colors()
{ {
for key in ${!map[@]} all_maps=$(echo ${COLOR_TABLE} | sed -e 's/:/ /g' -e 's/=[0-9a-fA-F]\{6\}//g')
debug_print "Going through all these colors ${all_maps} (${COLOR_TABLE})"
for key in ${all_maps}
do do
debug_print "Processing key ${key}"
case $key in case $key in
*bright*) text_color="000000" ;; *bright*) text_color="000000" ;;
*black*) text_color="ffffff" ;; *black*) text_color="ffffff" ;;
@@ -100,7 +123,7 @@ function dump_colors()
exit 0 exit 0
} }
function cube_row() cube_row()
{ {
r_base=${1} r_base=${1}
g=0 g=0
@@ -135,7 +158,7 @@ function cube_row()
printf '\n' printf '\n'
} }
function greyscale_chart() greyscale_chart()
{ {
[ ${i} -ge 244 ] && txt=";30" [ ${i} -ge 244 ] && txt=";30"
@@ -148,7 +171,7 @@ function greyscale_chart()
printf '\n' printf '\n'
} }
function color_chart() color_chart()
{ {
i=0 i=0
@@ -171,22 +194,22 @@ function color_chart()
greyscale_chart greyscale_chart
} }
function setup_color_depth() setup_color_depth()
{ {
use_8_bit=0 use_8_bit=0
use_4_bit=0 use_4_bit=0
use_3_bit=0 use_3_bit=0
# Must only be set if terminal colors are reduced... # Must only be set if terminal colors are reduced...
if [[ -v CSHENV_TERMINAL_COLORS ]] if [ "${CSHENV_TERMINAL_COLORS+x}" ]
then then
if (( ${CSHENV_TERMINAL_COLORS} == 256 )) if [ ${CSHENV_TERMINAL_COLORS} -eq 256 ]
then then
use_8_bit=1 use_8_bit=1
elif (( ${CSHENV_TERMINAL_COLORS} == 16 )) elif [ ${CSHENV_TERMINAL_COLORS} -eq 16 ]
then then
use_4_bit=1 use_4_bit=1
elif (( ${CSHENV_TERMINAL_COLORS} == 8 )) elif [ ${CSHENV_TERMINAL_COLORS} -eq 8 ]
then then
use_3_bit=1 use_3_bit=1
else else
@@ -195,23 +218,23 @@ function setup_color_depth()
fi fi
} }
function render_color() render_color()
{ {
command_color=30 command_color=30
if (( ${use_3_bit} )) if [ ${use_3_bit} -ne 0 ]
then then
# Underline colours need 8-bit or more # Underline colours need 8-bit or more
if (( ${background} > 1 )) ; then return; fi if [ ${background} -gt 1 ] ; then return; fi
basecolor=$(( ${command_color} + ${background}*10)) basecolor=$(( ${command_color} + ${background}*10))
next="$(( ${basecolor} + ${legacy_3_bit} ))" next="$(( ${basecolor} + ${legacy_3_bit} ))"
elif (( ${use_4_bit} )) elif [ ${use_4_bit} -ne 0 ]
then then
# Underline colours need 8-bit or more # Underline colours need 8-bit or more
if (( ${background} > 1 )) ; then return; fi if [ ${background} -gt 1 ] ; then return; fi
command_color=$(( ${command_color} + ${intensity_1_bit} * 60 )) command_color=$(( ${command_color} + ${intensity_1_bit} * 60 ))
basecolor=$(( ${command_color} + ${background}*10 )) basecolor=$(( ${command_color} + ${background}*10 ))
next="$(( ${basecolor} + ${legacy_3_bit} ))" next="$(( ${basecolor} + ${legacy_3_bit} ))"
elif (( ${use_8_bit} )) elif [ ${use_8_bit} -ne 0 ]
then then
basecolor=$(( 8 + ${command_color} + ${background}*10 )) basecolor=$(( 8 + ${command_color} + ${background}*10 ))
next="${basecolor};5;${ext_8_bit}" next="${basecolor};5;${ext_8_bit}"
@@ -223,12 +246,12 @@ function render_color()
function ansi_color() ansi_color()
{ {
if (( ${1} > 15 )) if [ ${1} -gt 15 ]
then then
echo "ERROR!" 1>&2 ; exit -1 echo "ERROR!" 1>&2 ; exit -1
elif (( ${1} >= 8 )) elif [ ${1} -ge 8 ]
then then
use_4_bit=1 use_4_bit=1
intensity_1_bit=1 intensity_1_bit=1
@@ -240,11 +263,11 @@ function ansi_color()
fi fi
} }
function ext_grey() ext_grey()
{ {
use_8_bit=1 use_8_bit=1
grey_val=${1} grey_val=${1}
if (( ${grey_val} < 0 || ${grey_val} > 23 )) if [ ${grey_val} -le 0 - ${grey_val} -gt 23 ]
then then
echo "ERROR!" 1>&2 ; exit -1 echo "ERROR!" 1>&2 ; exit -1
fi fi
@@ -260,10 +283,10 @@ function ext_rgb()
ext_8_bit=$(( 16 + 36 * ${red_ext_val} + 6 * ${green_ext_val} + ${blue_ext_val} )) ext_8_bit=$(( 16 + 36 * ${red_ext_val} + 6 * ${green_ext_val} + ${blue_ext_val} ))
} }
function ext_color() ext_color()
{ {
use_8_bit=1 use_8_bit=1
if (( ${1} > 255 )) if [ ${1} -gt 255 ]
then then
echo "ERROR!" 1>&2 ; exit -1 echo "ERROR!" 1>&2 ; exit -1
else else
@@ -271,7 +294,7 @@ function ext_color()
fi fi
} }
function rgb_color() rgb_color()
{ {
color=$1 color=$1
case $1 in case $1 in
@@ -287,11 +310,11 @@ function rgb_color()
green_dec=$((16#${green_hex})) green_dec=$((16#${green_hex}))
blue_dec=$((16#${blue_hex})) blue_dec=$((16#${blue_hex}))
if (( ${debug} != 0 )) if [ ${debug} -ne 0 ]
then then
echo "Red: $red_dec" debug_print "Red: $red_dec"
echo "Green: $green_dec" debug_print "Green: $green_dec"
echo "Blue: $blue_dec" debug_print "Blue: $blue_dec"
fi fi
# Now compute the rest of the stuff... # Now compute the rest of the stuff...
@@ -311,22 +334,22 @@ function rgb_color()
# If we can support an intensity bit, we'll turn that on too... # If we can support an intensity bit, we'll turn that on too...
# But we're going to stop using bold to set the colour "intense" # But we're going to stop using bold to set the colour "intense"
# We'll use the 9x and 10x forms... # We'll use the 9x and 10x forms...
if (( ${red_dec} >= 192 || ${green_dec} >= 192 || ${blue_dec} >= 192 )) if [ ${red_dec} -ge 192 -o ${green_dec} -ge 192 -o ${blue_dec} -ge 192 ]
then then
intensity_1_bit=1 intensity_1_bit=1
fi fi
if (( ${debug} != 0 )) if [ ${debug} -ne 0 ]
then then
echo "Red bit: " $red_1_bit debug_print "Red bit: " $red_1_bit
echo "Green bit: " $green_1_bit debug_print "Green bit: " $green_1_bit
echo "Blue bit: " $blue_1_bit debug_print "Blue bit: " $blue_1_bit
echo "Intensity bit: " $intensity_1_bit debug_print "Intensity bit: " $intensity_1_bit
fi fi
# Because grey is tricky, I'm going to special case it: # Because grey is tricky, I'm going to special case it:
if (( 1 && ${red_dec} == ${green_dec} && ${green_dec} == ${blue_dec} && ${red_dec} < 128 && ${red_dec} >= 80 )) if [ ${red_dec} -eq ${green_dec} -a ${green_dec} -eq ${blue_dec} -a ${red_dec} -lt 128 -a ${red_dec} -ge 80 ]
then then
red_1_bit=0 red_1_bit=0
green_1_bit=0 green_1_bit=0
@@ -335,28 +358,28 @@ function rgb_color()
fi fi
# Because brown is tricky, I'm going to special case it: # Because brown is tricky, I'm going to special case it:
if (( ${red_dec} > 128 && ${green_dec} > 80 && ${blue_dec} < 50 )) if [ ${red_dec} -gt 128 -a ${green_dec} -gt 80 -a ${blue_dec} -lt 50 ]
then then
red_1_bit=1 red_1_bit=1
green_1_bit=1 green_1_bit=1
blue_1_bit=0 blue_1_bit=0
fi fi
if (( ${debug} != 0 )) if [ ${debug} -ne 0 ]
then then
echo "Red bit: " $red_1_bit debug_print "Red bit: " $red_1_bit
echo "Green bit: " $green_1_bit debug_print "Green bit: " $green_1_bit
echo "Blue bit: " $blue_1_bit debug_print "Blue bit: " $blue_1_bit
echo "Intensity bit: " $intensity_1_bit debug_print "Intensity bit: " $intensity_1_bit
fi fi
# This lets us combine them for a legacy colour value in the legacy colour space... # This lets us combine them for a legacy colour value in the legacy colour space...
legacy_3_bit=$(( ( ${blue_1_bit} << 2 ) + ( ${green_1_bit} << 1 ) + ( ${red_1_bit} ) )) legacy_3_bit=$(( ( ${blue_1_bit} << 2 ) + ( ${green_1_bit} << 1 ) + ( ${red_1_bit} ) ))
if (( ${debug} != 0 )) if [ ${debug} -ne 0 ]
then then
echo "Legacy colour: " ${legacy_3_bit} " Intensity: " ${intensity_1_bit} debug_print "Legacy colour: " ${legacy_3_bit} " Intensity: " ${intensity_1_bit}
fi fi
# Now compute an extended colour cube placement (216 colours): # Now compute an extended colour cube placement (216 colours):
@@ -365,35 +388,35 @@ function rgb_color()
green_ext_val=$(( ${green_dec} * 6 / 256 )) green_ext_val=$(( ${green_dec} * 6 / 256 ))
blue_ext_val=$(( ${blue_dec} * 6 / 256 )) blue_ext_val=$(( ${blue_dec} * 6 / 256 ))
if (( ${debug} != 0 )) if [ ${debug} -ne 0 ]
then then
echo "Red ext: " ${red_ext_val} debug_print "Red ext: " ${red_ext_val}
echo "Green ext: " ${green_ext_val} debug_print "Green ext: " ${green_ext_val}
echo "Blue ext: " ${blue_ext_val} debug_print "Blue ext: " ${blue_ext_val}
fi fi
ext_8_bit=$(( 16 + 36 * ${red_ext_val} + 6 * ${green_ext_val} + ${blue_ext_val} )) ext_8_bit=$(( 16 + 36 * ${red_ext_val} + 6 * ${green_ext_val} + ${blue_ext_val} ))
if (( ${debug} )) if [ ${debug} -ne 0 ]
then then
echo "Computed 216 color cube: " ${ext_8_bit} debug_print "Computed 216 color cube: " ${ext_8_bit}
fi fi
# Check for precise greyscale, which would replace the above: # Check for precise greyscale, which would replace the above:
# (Note that precise greyscale is implied by a user explicitly making ALL of RGB the # (Note that precise greyscale is implied by a user explicitly making ALL of RGB the
# same value, thus demanding greyscale, rather than a subtle tinting...) # same value, thus demanding greyscale, rather than a subtle tinting...)
if (( ${red_dec} == ${green_dec} && ${green_dec} == ${blue_dec} )) if [ ${red_dec} -eq ${green_dec} -a ${green_dec} -eq ${blue_dec} ]
then then
ext_8_bit=$(( 232 + ${red_dec} * 24 / 256 )) ext_8_bit=$(( 232 + ${red_dec} * 24 / 256 ))
fi fi
if (( ${debug} )) if [ ${debug} -ne 0 ]
then then
echo "Computed 256 color choice, after applying greyscale scan: " ${ext_8_bit} debug_print "Computed 256 color choice, after applying greyscale scan: " ${ext_8_bit}
fi fi
if (( ${enable_greyscale_cheat} )) if [ ${enable_greyscale_cheat} -ne 0 ]
then then
# Check for SPECIFIC white and black and grey: # Check for SPECIFIC white and black and grey:
# The half-saturation, no saturation, and full saturation values are mapped # The half-saturation, no saturation, and full saturation values are mapped
@@ -403,34 +426,34 @@ function rgb_color()
# points in the 6x6x6 colour cube.) # points in the 6x6x6 colour cube.)
# #
# TODO: Only map the head and tail of the colour cube. # TODO: Only map the head and tail of the colour cube.
if (( ${red_dec} == ${green_dec} && ${green_dec} == ${blue_dec} )) if [ ${red_dec} -eq ${green_dec} -a ${green_dec} -eq ${blue_dec} ]
then then
if (( ${red_dec} == 0 )) if [ ${red_dec} -eq 0 ]
then then
ext_8_bit=0 ext_8_bit=0
elif (( ${red_dec} == 255 )) elif [ ${red_dec} -eq 255 ]
then then
ext_8_bit=15 ext_8_bit=15
elif (( ${red_dec} == 128 )) elif [ ${red_dec} -eq 128 ]
then then
ext_8_bit=8 ext_8_bit=8
elif (( ${red_dec} == 192 )) elif [ ${red_dec} -eq 192 ]
then then
ext_8_bit=7 ext_8_bit=7
fi fi
fi fi
fi fi
if (( ${debug} )) if [ ${debug} -ne 0 ]
then then
echo "Computed 256 color choice, after applying white and black scan: " ${ext_8_bit} debug_print "Computed 256 color choice, after applying white and black scan: " ${ext_8_bit}
fi fi
# We do NOT check for the base colours precisely. Since those 16 can be custom mapped by your terminal emulator, you just pass `ansi:0` thru # We do NOT check for the base colours precisely. Since those 16 can be custom mapped by your terminal emulator, you just pass `ansi:0` thru
# `ansi:15` to select them by ID. Or use `ext:0` thru `ext:255` to select a specific extended colour. # `ansi:15` to select them by ID. Or use `ext:0` thru `ext:255` to select a specific extended colour.
} }
function make_color() make_color()
{ {
setup_color_depth setup_color_depth
background=$1 # $1 is the background bit background=$1 # $1 is the background bit
@@ -438,19 +461,20 @@ function make_color()
render_color render_color
} }
function named_color() named_color()
{ {
name=${1,,} name=$( echo ${1} | awk '{print tolower($1)}' )
if [[ ! -v map[$name] ]] mapped=$(map ${name})
debug_print "${name} -> ${mapped}"
if [ -z "${mapped}" ]
then then
echo "ERROR!" 1>&2 ; exit -1 echo "ERROR!" 1>&2 ; exit -1
fi fi
mapped=${map[$name]}
rgb_color ${mapped} rgb_color ${mapped}
} }
function build_sgr_code() build_sgr_code()
{ {
case $1 in case $1 in
dump-colors) dump_colors ;; dump-colors) dump_colors ;;
@@ -469,41 +493,41 @@ function build_sgr_code()
doubleunder) next=21 ;; doubleunder) next=21 ;;
reveal) next=28 ;; reveal) next=28 ;;
ansi:*) make_color 0 ansi_color ${1:5} ;; ansi:*) make_color 0 ansi_color ${1#?????} ;;
ext:grey*) make_color 0 ext_grey ${1:8} ;; ext:grey*) make_color 0 ext_grey ${1#????????} ;;
ext:rgb*) make_color 0 ext_rgb ${1:7} ;; ext:rgb*) make_color 0 ext_rgb ${1#???????} ;;
ext:*) make_color 0 ext_color ${1:4} ;; ext:*) make_color 0 ext_color ${1#????} ;;
# 12-bit color also supported # 12-bit color also supported
[0-9a-fA-F][0-9a-fA-F][0-9a-fA-F]) make_color 0 rgb_color $1 ;; [0-9a-fA-F][0-9a-fA-F][0-9a-fA-F]) make_color 0 rgb_color $1 ;;
[0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F]) make_color 0 rgb_color $1 ;; [0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F]) make_color 0 rgb_color $1 ;;
fg:ansi:*) make_color 0 ansi_color ${1:8} ;; fg:ansi:*) make_color 0 ansi_color ${1#????????} ;;
fg:ext:grey*) make_color 0 ext_grey ${1:11} ;; fg:ext:grey*) make_color 0 ext_grey ${1#???????????} ;;
fg:ext:rgb*) make_color 0 ext_rgb ${1:10} ;; fg:ext:rgb*) make_color 0 ext_rgb ${1#??????????} ;;
fg:ext:*) make_color 0 ext_color ${1:7} ;; fg:ext:*) make_color 0 ext_color ${1#???????} ;;
# 12-bit color also supported # 12-bit color also supported
fg:[0-9a-fA-F][0-9a-fA-F][0-9a-fA-F]) make_color 0 rgb_color ${1:3} ;; fg:[0-9a-fA-F][0-9a-fA-F][0-9a-fA-F]) make_color 0 rgb_color ${1:???} ;;
fg:[0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F]) make_color 0 rgb_color ${1:3} ;; fg:[0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F]) make_color 0 rgb_color ${1:???} ;;
bg:ansi:*) make_color 1 ansi_color ${1:8} ;; bg:ansi:*) make_color 1 ansi_color ${1#????????} ;;
bg:ext:grey*) make_color 1 ext_grey ${1:11} ;; bg:ext:grey*) make_color 1 ext_grey ${1#???????????} ;;
bg:ext:rgb*) make_color 1 ext_rgb ${1:10} ;; bg:ext:rgb*) make_color 1 ext_rgb ${1#??????????} ;;
bg:ext:*) make_color 1 ext_color ${1:7} ;; bg:ext:*) make_color 1 ext_color ${1#???????} ;;
# 12-bit color also supported # 12-bit color also supported
bg:[0-9a-fA-F][0-9a-fA-F][0-9a-fA-F]) make_color 1 rgb_color ${1:3} ;; bg:[0-9a-fA-F][0-9a-fA-F][0-9a-fA-F]) make_color 1 rgb_color ${1#???} ;;
bg:[0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F]) make_color 1 rgb_color ${1:3} ;; bg:[0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F]) make_color 1 rgb_color ${1#???} ;;
ul:ext:*) make_color 2 ext_color ${1:7} ;; ul:ext:*) make_color 2 ext_color ${1#???????} ;;
# 12-bit color also supported # 12-bit color also supported
ul:[0-9a-fA-F][0-9a-fA-F][0-9a-fA-F]) make_color 2 rgb_color ${1:3} ;; ul:[0-9a-fA-F][0-9a-fA-F][0-9a-fA-F]) make_color 2 rgb_color ${1#???} ;;
ul:[0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F]) make_color 2 rgb_color ${1:3} ;; ul:[0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F]) make_color 2 rgb_color ${1#???} ;;
fg:*) make_color 0 named_color ${1:3} ;; fg:*) make_color 0 named_color ${1#???} ;;
bg:*) make_color 1 named_color ${1:3} ;; bg:*) make_color 1 named_color ${1#???} ;;
ul:*) make_color 2 named_color ${1:3} ;; ul:*) make_color 2 named_color ${1#???} ;;
*) make_color 0 named_color $1 ;; *) make_color 0 named_color $1 ;;
esac esac
if [[ ! -z ${output} ]] if [ ! -z ${output} ]
then then
output="${output};" output="${output};"
fi fi
@@ -511,17 +535,18 @@ function build_sgr_code()
} }
output="" output=""
while [[ ! -z $1 ]] while [ ! -z $1 ]
do do
build_sgr_code $1 build_sgr_code $1
shift 1 shift 1
done done
if [[ ! -z ${output} ]] && (( ! ${nocsi} )) if [ ! -z "${output}" ] && [ ${nocsi} -ne 1 ]
then then
#echo "I AM PRINTING WITH CSI! (${nocsi} is nocsi, \`${output}\` is output)">&2
output="[${output}m" output="[${output}m"
fi fi
if [[ ! -z ${output} ]] if [ ! -z "${output}" ]
then then
printf ${output} printf ${output}
fi fi