Convert color script to portable POSIX shell

This commit is contained in:
2026-06-22 19:33:17 -04:00
parent 8db7a5a13e
commit 9ed5dfd09b
+156 -131
View File
@@ -13,54 +13,74 @@
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...
declare -A map
map[white]="FFFFFF"
map[silver]="C0C0C0"
map[gray]="808080"
map[grey]="808080"
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"
COLOR_TABLE=""
map()
{
if [ $# -eq 2 ]
then
COLOR_TABLE="${COLOR_TABLE}:${1}=${2}"
else
___right="${COLOR_TABLE##*:${1}=}"
echo ${___right%%:*}
fi
}
map white "FFFFFF"
map silver "C0C0C0"
map gray "808080"
map grey "808080"
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:
map[brown]="aa5500"
map[cyan]="00aaaa"
map brown "aa5500"
map cyan "00aaaa"
# 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.
map[vga-black]="000000"
map[vga-red]="aa0000"
map[vga-green]="00aa00"
map[vga-brown]="aa5500"
map[vga-yellow]="aa5500"
map[vga-blue]="0000aa"
map[vga-magenta]="aa00aa"
map[vga-cyan]="00aaaa"
map[vga-white]="aaaaaa"
map vga-black "000000"
map vga-red "aa0000"
map vga-green "00aa00"
map vga-brown "aa5500"
map vga-yellow "aa5500"
map vga-blue "0000aa"
map vga-magenta "aa00aa"
map vga-cyan "00aaaa"
map vga-white "aaaaaa"
map[vga-brightblack]="555555"
map[vga-grey]="555555"
map[vga-gray]="555555"
map[vga-brightred]="ff5555"
map[vga-brightgreen]="55ff55"
map[vga-brightyellow]="ffff55"
map[vga-brightblue]="5555ff"
map[vga-brightmagenta]="ff55ff"
map[vga-brightcyan]="55ffff"
map[vga-brightwhite]="ffffff"
map vga-brightblack "555555"
map vga-grey "555555"
map vga-gray "555555"
map vga-brightred "ff5555"
map vga-brightgreen "55ff55"
map vga-brightyellow "ffff55"
map vga-brightblue "5555ff"
map vga-brightmagenta "ff55ff"
map vga-brightcyan "55ffff"
map vga-brightwhite "ffffff"
enable_greyscale_cheat=0
@@ -71,10 +91,13 @@ then
shift 1
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
debug_print "Processing key ${key}"
case $key in
*bright*) text_color="000000" ;;
*black*) text_color="ffffff" ;;
@@ -100,7 +123,7 @@ function dump_colors()
exit 0
}
function cube_row()
cube_row()
{
r_base=${1}
g=0
@@ -135,7 +158,7 @@ function cube_row()
printf '\n'
}
function greyscale_chart()
greyscale_chart()
{
[ ${i} -ge 244 ] && txt=";30"
@@ -148,7 +171,7 @@ function greyscale_chart()
printf '\n'
}
function color_chart()
color_chart()
{
i=0
@@ -171,22 +194,22 @@ function color_chart()
greyscale_chart
}
function setup_color_depth()
setup_color_depth()
{
use_8_bit=0
use_4_bit=0
use_3_bit=0
# Must only be set if terminal colors are reduced...
if [[ -v CSHENV_TERMINAL_COLORS ]]
if [ "${CSHENV_TERMINAL_COLORS+x}" ]
then
if (( ${CSHENV_TERMINAL_COLORS} == 256 ))
if [ ${CSHENV_TERMINAL_COLORS} -eq 256 ]
then
use_8_bit=1
elif (( ${CSHENV_TERMINAL_COLORS} == 16 ))
elif [ ${CSHENV_TERMINAL_COLORS} -eq 16 ]
then
use_4_bit=1
elif (( ${CSHENV_TERMINAL_COLORS} == 8 ))
elif [ ${CSHENV_TERMINAL_COLORS} -eq 8 ]
then
use_3_bit=1
else
@@ -195,23 +218,23 @@ function setup_color_depth()
fi
}
function render_color()
render_color()
{
command_color=30
if (( ${use_3_bit} ))
if [ ${use_3_bit} -ne 0 ]
then
# 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))
next="$(( ${basecolor} + ${legacy_3_bit} ))"
elif (( ${use_4_bit} ))
elif [ ${use_4_bit} -ne 0 ]
then
# 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 ))
basecolor=$(( ${command_color} + ${background}*10 ))
next="$(( ${basecolor} + ${legacy_3_bit} ))"
elif (( ${use_8_bit} ))
elif [ ${use_8_bit} -ne 0 ]
then
basecolor=$(( 8 + ${command_color} + ${background}*10 ))
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
echo "ERROR!" 1>&2 ; exit -1
elif (( ${1} >= 8 ))
elif [ ${1} -ge 8 ]
then
use_4_bit=1
intensity_1_bit=1
@@ -240,11 +263,11 @@ function ansi_color()
fi
}
function ext_grey()
ext_grey()
{
use_8_bit=1
grey_val=${1}
if (( ${grey_val} < 0 || ${grey_val} > 23 ))
if [ ${grey_val} -le 0 - ${grey_val} -gt 23 ]
then
echo "ERROR!" 1>&2 ; exit -1
fi
@@ -260,10 +283,10 @@ function ext_rgb()
ext_8_bit=$(( 16 + 36 * ${red_ext_val} + 6 * ${green_ext_val} + ${blue_ext_val} ))
}
function ext_color()
ext_color()
{
use_8_bit=1
if (( ${1} > 255 ))
if [ ${1} -gt 255 ]
then
echo "ERROR!" 1>&2 ; exit -1
else
@@ -271,7 +294,7 @@ function ext_color()
fi
}
function rgb_color()
rgb_color()
{
color=$1
case $1 in
@@ -287,11 +310,11 @@ function rgb_color()
green_dec=$((16#${green_hex}))
blue_dec=$((16#${blue_hex}))
if (( ${debug} != 0 ))
if [ ${debug} -ne 0 ]
then
echo "Red: $red_dec"
echo "Green: $green_dec"
echo "Blue: $blue_dec"
debug_print "Red: $red_dec"
debug_print "Green: $green_dec"
debug_print "Blue: $blue_dec"
fi
# 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...
# But we're going to stop using bold to set the colour "intense"
# 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
intensity_1_bit=1
fi
if (( ${debug} != 0 ))
if [ ${debug} -ne 0 ]
then
echo "Red bit: " $red_1_bit
echo "Green bit: " $green_1_bit
echo "Blue bit: " $blue_1_bit
echo "Intensity bit: " $intensity_1_bit
debug_print "Red bit: " $red_1_bit
debug_print "Green bit: " $green_1_bit
debug_print "Blue bit: " $blue_1_bit
debug_print "Intensity bit: " $intensity_1_bit
fi
# 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
red_1_bit=0
green_1_bit=0
@@ -335,28 +358,28 @@ function rgb_color()
fi
# 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
red_1_bit=1
green_1_bit=1
blue_1_bit=0
fi
if (( ${debug} != 0 ))
if [ ${debug} -ne 0 ]
then
echo "Red bit: " $red_1_bit
echo "Green bit: " $green_1_bit
echo "Blue bit: " $blue_1_bit
echo "Intensity bit: " $intensity_1_bit
debug_print "Red bit: " $red_1_bit
debug_print "Green bit: " $green_1_bit
debug_print "Blue bit: " $blue_1_bit
debug_print "Intensity bit: " $intensity_1_bit
fi
# 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} ) ))
if (( ${debug} != 0 ))
if [ ${debug} -ne 0 ]
then
echo "Legacy colour: " ${legacy_3_bit} " Intensity: " ${intensity_1_bit}
debug_print "Legacy colour: " ${legacy_3_bit} " Intensity: " ${intensity_1_bit}
fi
# Now compute an extended colour cube placement (216 colours):
@@ -365,35 +388,35 @@ function rgb_color()
green_ext_val=$(( ${green_dec} * 6 / 256 ))
blue_ext_val=$(( ${blue_dec} * 6 / 256 ))
if (( ${debug} != 0 ))
if [ ${debug} -ne 0 ]
then
echo "Red ext: " ${red_ext_val}
echo "Green ext: " ${green_ext_val}
echo "Blue ext: " ${blue_ext_val}
debug_print "Red ext: " ${red_ext_val}
debug_print "Green ext: " ${green_ext_val}
debug_print "Blue ext: " ${blue_ext_val}
fi
ext_8_bit=$(( 16 + 36 * ${red_ext_val} + 6 * ${green_ext_val} + ${blue_ext_val} ))
if (( ${debug} ))
if [ ${debug} -ne 0 ]
then
echo "Computed 216 color cube: " ${ext_8_bit}
debug_print "Computed 216 color cube: " ${ext_8_bit}
fi
# Check for precise greyscale, which would replace the above:
# (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...)
if (( ${red_dec} == ${green_dec} && ${green_dec} == ${blue_dec} ))
if [ ${red_dec} -eq ${green_dec} -a ${green_dec} -eq ${blue_dec} ]
then
ext_8_bit=$(( 232 + ${red_dec} * 24 / 256 ))
fi
if (( ${debug} ))
if [ ${debug} -ne 0 ]
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
if (( ${enable_greyscale_cheat} ))
if [ ${enable_greyscale_cheat} -ne 0 ]
then
# Check for SPECIFIC white and black and grey:
# The half-saturation, no saturation, and full saturation values are mapped
@@ -403,34 +426,34 @@ function rgb_color()
# points in the 6x6x6 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
if (( ${red_dec} == 0 ))
if [ ${red_dec} -eq 0 ]
then
ext_8_bit=0
elif (( ${red_dec} == 255 ))
elif [ ${red_dec} -eq 255 ]
then
ext_8_bit=15
elif (( ${red_dec} == 128 ))
elif [ ${red_dec} -eq 128 ]
then
ext_8_bit=8
elif (( ${red_dec} == 192 ))
elif [ ${red_dec} -eq 192 ]
then
ext_8_bit=7
fi
fi
fi
if (( ${debug} ))
if [ ${debug} -ne 0 ]
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
# 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.
}
function make_color()
make_color()
{
setup_color_depth
background=$1 # $1 is the background bit
@@ -438,19 +461,20 @@ function make_color()
render_color
}
function named_color()
named_color()
{
name=${1,,}
if [[ ! -v map[$name] ]]
name=$( echo ${1} | awk '{print tolower($1)}' )
mapped=$(map ${name})
debug_print "${name} -> ${mapped}"
if [ -z "${mapped}" ]
then
echo "ERROR!" 1>&2 ; exit -1
fi
mapped=${map[$name]}
rgb_color ${mapped}
}
function build_sgr_code()
build_sgr_code()
{
case $1 in
dump-colors) dump_colors ;;
@@ -469,41 +493,41 @@ function build_sgr_code()
doubleunder) next=21 ;;
reveal) next=28 ;;
ansi:*) make_color 0 ansi_color ${1:5} ;;
ext:grey*) make_color 0 ext_grey ${1:8} ;;
ext:rgb*) make_color 0 ext_rgb ${1:7} ;;
ext:*) make_color 0 ext_color ${1:4} ;;
ansi:*) make_color 0 ansi_color ${1#?????} ;;
ext:grey*) make_color 0 ext_grey ${1#????????} ;;
ext:rgb*) make_color 0 ext_rgb ${1#???????} ;;
ext:*) make_color 0 ext_color ${1#????} ;;
# 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][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:ext:grey*) make_color 0 ext_grey ${1:11} ;;
fg:ext:rgb*) make_color 0 ext_rgb ${1:10} ;;
fg:ext:*) make_color 0 ext_color ${1:7} ;;
fg:ansi:*) make_color 0 ansi_color ${1#????????} ;;
fg:ext:grey*) make_color 0 ext_grey ${1#???????????} ;;
fg:ext:rgb*) make_color 0 ext_rgb ${1#??????????} ;;
fg:ext:*) make_color 0 ext_color ${1#???????} ;;
# 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][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:???} ;;
bg:ansi:*) make_color 1 ansi_color ${1:8} ;;
bg:ext:grey*) make_color 1 ext_grey ${1:11} ;;
bg:ext:rgb*) make_color 1 ext_rgb ${1:10} ;;
bg:ext:*) make_color 1 ext_color ${1:7} ;;
bg:ansi:*) make_color 1 ansi_color ${1#????????} ;;
bg:ext:grey*) make_color 1 ext_grey ${1#???????????} ;;
bg:ext:rgb*) make_color 1 ext_rgb ${1#??????????} ;;
bg:ext:*) make_color 1 ext_color ${1#???????} ;;
# 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][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#???} ;;
ul:ext:*) make_color 2 ext_color ${1:7} ;;
ul:ext:*) make_color 2 ext_color ${1#???????} ;;
# 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][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#???} ;;
fg:*) make_color 0 named_color ${1:3} ;;
bg:*) make_color 1 named_color ${1:3} ;;
ul:*) make_color 2 named_color ${1:3} ;;
fg:*) make_color 0 named_color ${1#???} ;;
bg:*) make_color 1 named_color ${1#???} ;;
ul:*) make_color 2 named_color ${1#???} ;;
*) make_color 0 named_color $1 ;;
esac
if [[ ! -z ${output} ]]
if [ ! -z ${output} ]
then
output="${output};"
fi
@@ -511,17 +535,18 @@ function build_sgr_code()
}
output=""
while [[ ! -z $1 ]]
while [ ! -z $1 ]
do
build_sgr_code $1
shift 1
done
if [[ ! -z ${output} ]] && (( ! ${nocsi} ))
if [ ! -z "${output}" ] && [ ${nocsi} -ne 1 ]
then
#echo "I AM PRINTING WITH CSI! (${nocsi} is nocsi, \`${output}\` is output)">&2
output="[${output}m"
fi
if [[ ! -z ${output} ]]
if [ ! -z "${output}" ]
then
printf ${output}
fi