From 9ed5dfd09ba93736e9289b21f8fd2abc6091fbf2 Mon Sep 17 00:00:00 2001 From: ADAM David Alan Martin Date: Mon, 22 Jun 2026 19:33:17 -0400 Subject: [PATCH] Convert color script to portable POSIX shell --- bin/__build_sgr_code | 291 +++++++++++++++++++++++-------------------- 1 file changed, 158 insertions(+), 133 deletions(-) diff --git a/bin/__build_sgr_code b/bin/__build_sgr_code index 0b025e9..29deac6 100755 --- a/bin/__build_sgr_code +++ b/bin/__build_sgr_code @@ -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 )) - then - echo "Red: $red_dec" - echo "Green: $green_dec" - echo "Blue: $blue_dec" + if [ ${debug} -ne 0 ] + then + 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 - fi + 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