Last active
December 4, 2025 05:31
-
-
Save AlexAtkinson/cba46af65237291a307835be007072c8 to your computer and use it in GitHub Desktop.
printFancyHeader.sh
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env bash | |
| # shellcheck disable=2183 | |
| # GIST NOTES | |
| # v1 is here: https://gist.github.com/AlexAtkinson/49078eb9a2dcff17b28371eb92964cca | |
| # Clean Test - run: | |
| # NOTE: The hash in the URL below should match this gist. <<< VERIFY THIS | |
| # docker run -e LANG="C.UTF-8" -e LC_ALL="C.UTF-8" -it debian /bin/bash | |
| # apt update && apt -y install curl | |
| # source <(curl -s https://gist.githubusercontent.com/AlexAtkinson/cba46af65237291a307835be007072c8/raw/printFancyHeader.sh) | |
| # for opt in " " "-e"; do for i in 0 $(($(tput cols) / 8)) $(($(tput cols) / 8 +1)) $(($(tput cols) / 2)) $(($(tput cols) +10)); do for t in "H" "XX"; do printFancyHeader -w $i -g -t "$t" "$opt" ; done; done; done | |
| # for opt in " " "-e"; do printFancyHeader -t "$(printf '%*s' "$(( $(tput cols) +10 ))" | sed "s/ /X/g")" "$opt"; done | |
| # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | |
| # Prints a fancy section header. | |
| # See help menu for details. | |
| # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | |
| function printFancyHeader() { | |
| help() { | |
| cat << EOF | |
| Print a fancy heading. | |
| Use: ${0##*/} [-w <int>] [-f <bool>] [-v <char>] [-h <char>] [-i <char>] [-t <string(s)>] | |
| -w INT The width of the header Default: Full Screen | |
| Minimum: Title + 8 | |
| Note: If odd, this program will subtract 1. | |
| -v CHAR Vertical border character Default: \# | |
| -h CHAR Horizontal border character Default: = | |
| -i CHAR Internal fill character Default: - | |
| -t TITLE "Title Text" (double quoted) Default: Hello World! | |
| -f Fill Default: true | |
| -g Gutters Default: false | |
| -e Emoji Mode Default: Disabled | |
| Fullwidth/thick (DOUBLE SPACE) emoji's only. | |
| Auto-transforms: | |
| [a-zA-Z0-9]( :;<>?!#$&()*+-_~|{}@[]^) | |
| Some characters require escapes(\). | |
| 🛸 https://www.amp-what.com/unicode/search/fullwidth 🛸 | |
| NOTE: Some characters require escaping. | |
| Examples: | |
| printFancyHeader -w 20 -t Cool Title | |
| printFancyHeader -w 60 -v \* -h \~ -i . -t "Cool Title" -f false | |
| printFancyHeader -v 🐵 -h 🛻 -i 🌭 -t "Monkey Business" -e -g -w $(($(tput cols)/2)) | |
| printFancyHeader -t "[ Hello World ]" -e -w 44 | |
| EOF | |
| } | |
| visual_width() { | |
| local str width char char_bytes | |
| str="$1" | |
| if width=$(echo -n "$str" | wc -L 2>/dev/null); then | |
| echo "$width" | |
| return | |
| fi | |
| local width=0 | |
| local i | |
| for ((i=0; i<${#str}; i++)); do | |
| char="${str:$i:1}" | |
| char_bytes=$(echo -n "$char" | wc -c) | |
| if (( char_bytes > 1 )); then # Emoji | |
| ((width += 2)) | |
| else # ASCII | |
| ((width += 1)) | |
| fi | |
| done | |
| echo "$width" | |
| } | |
| repeat_to_width() { | |
| local char target_width current_width result char_width count i max_iterations iterations pad_char space_char | |
| char="$1" | |
| target_width="$2" | |
| space_char="$3" | |
| result="" | |
| char_width=$(visual_width "$space_char") | |
| (( char_width == 0 )) && char_width=1 | |
| if (( target_width <= 0 )); then | |
| echo "" | |
| return | |
| fi | |
| (( count = target_width / char_width )) | |
| for ((i=0; i<count; i++)); do | |
| result+="$char" | |
| done | |
| current_width=$(visual_width "$result") | |
| max_iterations=10 | |
| iterations=0 | |
| while (( current_width < target_width && iterations < max_iterations )); do | |
| result+="$space_char" | |
| current_width=$(visual_width "$result") | |
| ((iterations++)) | |
| done | |
| while (( current_width > target_width && ${#result} > 0 )); do | |
| result="${result::-$char_width}" | |
| current_width=$(visual_width "$result") | |
| done | |
| echo "$result" | |
| } | |
| # Emoji Character Dictionary | |
| declare -rA e_c=( | |
| ["a"]="a" ["b"]="b" ["c"]="c" ["d"]="d" ["e"]="e" ["f"]="f" ["g"]="g" | |
| ["h"]="h" ["i"]="i" ["j"]="j" ["k"]="k" ["l"]="l" ["m"]="m" | |
| ["n"]="n" ["o"]="o" ["p"]="p" ["q"]="q" ["r"]="r" ["s"]="s" ["t"]="t" | |
| ["u"]="u" ["v"]="v" ["w"]="w" ["x"]="x" ["y"]="y" ["z"]="z" | |
| ["A"]="A" ["B"]="B" ["C"]="C" ["D"]="D" ["E"]="E" ["F"]="F" ["G"]="G" | |
| ["H"]="H" ["I"]="I" ["J"]="J" ["K"]="K" ["L"]="L" ["M"]="M" | |
| ["N"]="N" ["O"]="O" ["P"]="P" ["Q"]="Q" ["R"]="R" ["S"]="S" ["T"]="T" | |
| ["U"]="U" ["V"]="V" ["W"]="W" ["X"]="X" ["Y"]="Y" ["Z"]="Z" | |
| ["0"]="0" ["1"]="1" ["2"]="2" ["3"]="3" ["4"]="4" | |
| ["5"]="5" ["6"]="6" ["7"]="7" ["8"]="8" ["9"]="9" | |
| [" "]=" " [":"]=":" [";"]=";" ["<"]="<" [">"]=">" ["?"]="?" ["!"]="!" | |
| ["#"]="#" ["$"]="$" ["&"]="&" ["("]="(" [")"]=")" ["*"]="*" ["-"]="-" | |
| ["+"]="+" ["_"]="_" ["~"]="~" ["|"]="|" ["{"]="{" ["}"]="}" ["@"]="@" | |
| ["["]="[" ["]"]="]" ["^"]="^" ["."]=". " | |
| ) | |
| local txt width min_width max_width target_width \ | |
| vert_c horz_c intr_c ogut_c igut_c space_c \ | |
| vert_width ogut_width igut_width space_width \ | |
| fill gut emoji \ | |
| title_text title_visual_width \ | |
| border_width content_width fill_width_l fill_width_r \ | |
| horz_border_fill intr_fill intr_half_fill_l intr_half_fill_r \ | |
| c char_width padding max_title_width truncated current_width | |
| txt=() | |
| OPTIND=1 | |
| while getopts "w:v:h:i:fget:" OPT; do | |
| case "$OPT" in | |
| w) width="$OPTARG" ;; | |
| v) vert_c="$OPTARG" ;; | |
| h) horz_c="$OPTARG" ;; | |
| i) intr_c="$OPTARG" ;; | |
| f) fill="false" ;; | |
| g) gut="true" ;; | |
| e) emoji="true" ;; | |
| t) set -f | |
| IFS=' ' | |
| # shellcheck disable=2206 | |
| txt=($OPTARG) ;; | |
| :) echo "ERROR: -$OPTARG requires an argument." | |
| help; return 1 ;; | |
| *) help; return 1 ;; | |
| esac | |
| done | |
| shift $((OPTIND-1)) | |
| set +f | |
| max_width=$(tput cols) # Maximum box width (terminal width) | |
| width="${width:-$max_width}" # Title box width | |
| fill="${fill:-true}" # Fill option | |
| gut="${gut:-false}" # Gutter option | |
| # Title Text Control | |
| # shellcheck disable=2206 | |
| txt=(${txt[@]:-Hello World\!}) # Title text | |
| if [[ "$emoji" == "true" ]]; then # Emoji Transform | |
| c="${txt[*]}" | |
| # shellcheck disable=2207 | |
| txt=($(for ((i=0; i<${#c}; i++)); do | |
| printf "%s" "${e_c["${c:$i:1}"]}" | |
| done)) | |
| fi | |
| title_text="${txt[*]}" | |
| title_visual_width=$(visual_width "$title_text") | |
| if [[ "$emoji" != "true" ]]; then # Default characters | |
| [[ "$fill" == "false" ]] && intr_c=' ' | |
| vert_c="${vert_c:-#}" | |
| horz_c="${horz_c:-=}" | |
| intr_c="${intr_c:--}" | |
| space_c=" " | |
| ogut_c=" " | |
| igut_c=" " | |
| else | |
| [[ "$fill" == "false" ]] && intr_c="${e_c[" "]}" | |
| vert_c="${vert_c:-🛸}" | |
| horz_c="${horz_c:-🛸}" | |
| intr_c="${intr_c:-👽}" | |
| space_c="${e_c[" "]}" | |
| ogut_c="${e_c[" "]}" | |
| igut_c="${e_c[" "]}" | |
| fi | |
| if [[ "$gut" == "false" ]]; then # Gutter | |
| ogut_c="$horz_c" | |
| igut_c="$intr_c" | |
| fi | |
| vert_width=$(visual_width "$vert_c") # Element widths | |
| ogut_width=$(visual_width "$ogut_c") | |
| igut_width=$(visual_width "$igut_c") | |
| space_width=$(visual_width "$space_c") | |
| local min_fill_char_width # Minimum fill | |
| min_fill_char_width=$(visual_width "$intr_c") | |
| padding=$((2 * vert_width + 2 * igut_width + 2 * space_width + 2 * min_fill_char_width)) | |
| max_title_width=$((max_width - padding - 3)) # 3 for ellipses. Truncate if too long | |
| if (( title_visual_width > max_title_width )); then | |
| # Truncate and add ellipses | |
| truncated="" | |
| current_width=0 | |
| for ((i=0; i<${#title_text}; i++)); do | |
| char="${title_text:$i:1}" | |
| char_width=$(visual_width "$char") | |
| if (( current_width + char_width + 3 > max_title_width )); then | |
| break | |
| fi | |
| truncated+="$char" | |
| ((current_width += char_width)) | |
| done | |
| [[ "$emoji" == "true" ]] || title_text="${truncated}..." | |
| [[ "$emoji" == "true" ]] && title_text="${truncated}. . . " | |
| title_visual_width=$(visual_width "$title_text") | |
| fi | |
| min_width=$((title_visual_width + padding)) # Box Width | |
| (( width < min_width )) && width=$min_width | |
| (( width > max_width )) && width=$max_width | |
| border_width=$((width - 2 * vert_width - 2 * ogut_width)) # Border Width | |
| content_width=$((width - 2 * vert_width - 2 * igut_width)) # Content row | |
| title_with_spaces_width=$((2 * space_width + title_visual_width)) # Title row | |
| total_fill_width=$((content_width - title_with_spaces_width)) | |
| # Ensure minimum width | |
| if (( total_fill_width < 0 )); then | |
| content_width=$((title_with_spaces_width + 2)) | |
| width=$((content_width + 2 * vert_width + 2 * igut_width)) | |
| border_width=$((width - 2 * vert_width - 2 * ogut_width)) | |
| total_fill_width=2 | |
| fi | |
| fill_width_l=$((total_fill_width / 2)) | |
| fill_width_r=$((total_fill_width - fill_width_l)) # Observes odd widths | |
| # Ensure minimum fill of 1 | |
| (( fill_width_l < 1 )) && fill_width_l=1 | |
| (( fill_width_r < 1 )) && fill_width_r=1 | |
| # Width offset for emoji mode | |
| if [[ "$emoji" == "true" ]]; then | |
| (( content_width%2 )) || (( fill_width_r++ )) | |
| if (( content_width > min_width )); then | |
| (( fill_width_l > 2 )) && (( fill_width_l-- )) | |
| (( fill_width_l > 2 )) && (( fill_width_r++ )) | |
| fi | |
| fi | |
| horz_border_fill=$(repeat_to_width "$horz_c" "$border_width" "$space_c") | |
| intr_fill=$(repeat_to_width "$intr_c" "$content_width" "$space_c") | |
| intr_half_fill_l=$(repeat_to_width "$intr_c" "$fill_width_l" "$space_c") | |
| intr_half_fill_r=$(repeat_to_width "$intr_c" "$fill_width_r" "$space_c") | |
| #echo "w: $width, bw: $border_width, fl: $fill_width_l, fr: $fill_width_r, tfl: $total_fill_width, tl: $title_visual_width" | |
| printf '%s\n' "${vert_c}${ogut_c}${horz_border_fill}${ogut_c}${vert_c}" | |
| printf '%s\n' "${vert_c}${igut_c}${intr_fill}${igut_c}${vert_c}" | |
| printf '%s' "${vert_c}${igut_c}${intr_half_fill_l}" | |
| printf "%b" "\e[01;39m${space_c}${title_text}${space_c}\e[0m" | |
| printf '%s\n' "${intr_half_fill_r}${igut_c}${vert_c}" | |
| printf '%s\n' "${vert_c}${igut_c}${intr_fill}${igut_c}${vert_c}" | |
| printf '%s\n' "${vert_c}${ogut_c}${horz_border_fill}${ogut_c}${vert_c}" | |
| } |
Author
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
EG:
