colr.sh 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329
  1. #!/bin/bash
  2. # Bash color function to colorize text by name, instead of number.
  3. # Also includes maps from name to escape code for fore, back, and styles.
  4. # -Christopher Welborn 08-27-2015
  5. # Variables are namespaced to not interfere when sourced.
  6. colr_app_name="Colr"
  7. colr_app_version="0.4.0"
  8. colr_app_path="$(readlink -f "${BASH_SOURCE[0]}")"
  9. colr_app_script="${colr_app_path##*/}"
  10. # This flag can be set with colr_enable or colr_disable.
  11. colr_disabled=0
  12. # Functions to format a color number into an actual escape code.
  13. function codeformat {
  14. # Basic fore, back, and styles.
  15. printf "\033[%sm" "$1"
  16. }
  17. function extforeformat {
  18. # 256 fore color
  19. printf "\033[38;5;%sm" "$1"
  20. }
  21. function extbackformat {
  22. # 256 back color
  23. printf "\033[48;5;%sm" "$1"
  24. }
  25. # Maps from color/style name -> escape code.
  26. declare -A fore back style
  27. function build_maps {
  28. # Build the fore/back maps.
  29. # Names and corresponding base code number
  30. local colornum
  31. # shellcheck disable=SC2102
  32. declare -A colornum=(
  33. [black]=0
  34. [red]=1
  35. [green]=2
  36. [yellow]=3
  37. [blue]=4
  38. [magenta]=5
  39. [cyan]=6
  40. [white]=7
  41. )
  42. local cname
  43. for cname in "${!colornum[@]}"; do
  44. fore[$cname]="$(codeformat $((30 + ${colornum[$cname]})))"
  45. fore[light$cname]="$(codeformat $((90 + ${colornum[$cname]})))"
  46. back[$cname]="$(codeformat $((40 + ${colornum[$cname]})))"
  47. back[light$cname]="$(codeformat $((100 + ${colornum[$cname]})))"
  48. done
  49. # shellcheck disable=SC2154
  50. fore[reset]="$(codeformat 39)"
  51. back[reset]="$(codeformat 49)"
  52. # 256 colors.
  53. local cnum
  54. for cnum in {0..255}; do
  55. fore[$cnum]="$(extforeformat "$cnum")"
  56. back[$cnum]="$(extbackformat "$cnum")"
  57. done
  58. # Map of base code -> style name
  59. local stylenum
  60. # shellcheck disable=SC2102
  61. declare -A stylenum=(
  62. [reset]=0
  63. [bright]=1
  64. [dim]=2
  65. [italic]=3
  66. [underline]=4
  67. [flash]=5
  68. [highlight]=7
  69. [normal]=22
  70. )
  71. local sname
  72. for sname in "${!stylenum[@]}"; do
  73. style[$sname]="$(codeformat "${stylenum[$sname]}")"
  74. done
  75. }
  76. build_maps
  77. function colr {
  78. # Colorize a string.
  79. local text="$1"
  80. if ((colr_disabled)); then
  81. # Color has been globally disabled.
  82. echo -en "$text"
  83. return
  84. fi
  85. local forecolr="${2:-reset}"
  86. local backcolr="${3:-reset}"
  87. local stylename="${4:-normal}"
  88. declare -a codes resetcodes
  89. if [[ "$stylename" =~ ^reset ]]; then
  90. resetcodes=("${style[$stylename]}" "${resetcodes[@]}")
  91. else
  92. codes=("${codes[@]}" "${style[$stylename]}")
  93. fi
  94. if [[ "$backcolr" =~ reset ]]; then
  95. resetcodes=("${back[$backcolr]}" "${resetcodes[@]}")
  96. else
  97. codes=("${codes[@]}" "${back[$backcolr]}")
  98. fi
  99. if [[ "$forecolr" =~ reset ]]; then
  100. resetcodes=("${fore[$forecolr]}" "${resetcodes[@]}")
  101. else
  102. codes=("${codes[@]}" "${fore[$forecolr]}")
  103. fi
  104. # Reset codes must come first (style reset can affect colors)
  105. local rc
  106. for rc in "${resetcodes[@]}"; do
  107. echo -en "$rc"
  108. done
  109. local c
  110. for c in "${codes[@]}"; do
  111. echo -en "$c"
  112. done
  113. local closing="\033[m"
  114. echo -n "$text"
  115. echo -en "$closing"
  116. }
  117. function colr_auto_disable {
  118. # Auto disable colors if stdout is not a tty,
  119. # or if the user supplied file descriptors are not ttys.
  120. # Arguments:
  121. # $@ : One or more TTY numbers to check.
  122. # Default: 1
  123. if (($# == 0)); then
  124. # Just check stdout by default.
  125. if [[ ! -t 1 ]] || [[ -p 1 ]]; then
  126. colr_disabled=1
  127. fi
  128. return
  129. fi
  130. # Make sure all user's tty args are ttys.
  131. local ttynum
  132. for ttynum in "$@"; do
  133. if [[ ! -t "$ttynum" ]] || [[ -p "$ttynum" ]]; then
  134. colr_disabled=1
  135. break
  136. fi
  137. done
  138. }
  139. function colr_enable {
  140. # Re-enable colors after colr_disable has been called.
  141. colr_disabled=0
  142. }
  143. function colr_disable {
  144. # Disable colors for the `colr` function.
  145. colr_disabled=1
  146. }
  147. function colr_is_disabled {
  148. # Returns success code if colr_disabled is non-zero.
  149. ((colr_disabled)) && return 0
  150. return 1
  151. }
  152. function colr_is_enabled {
  153. # Returns success code if colr_disabled is zero.
  154. ((colr_disabled)) && return 1
  155. return 0
  156. }
  157. function echo_err {
  158. # Print to stderr.
  159. printf "%s " "$@" 1>&2
  160. printf "\n" 1>&2
  161. }
  162. function escape_code_repr {
  163. # Print the representation of an escape code,
  164. # without escaping (without setting a color, style, etc.)
  165. # This will replace all escape codes in a string with their
  166. # representation.
  167. # Arguments:
  168. # $@ : The escape codes or strings to show.
  169. (($#)) || {
  170. echo_err "No arguments passed to escape_code_repr."
  171. return 1
  172. }
  173. local escapecode
  174. for escapecode; do
  175. printf "%s" "${escapecode//$'\033'/$'\\033'}"
  176. done
  177. }
  178. function print_usage {
  179. # Show usage reason if first arg is available.
  180. [[ -n "$1" ]] && echo -e "\n$1\n"
  181. local b="${fore[blue]}" B="${style[bright]}" R="${style[reset]}"
  182. local g="${fore[green]}" y="${fore[yellow]}"
  183. local name=$colr_app_name script=$colr_app_script ver=$colr_app_version
  184. echo "${b}${B}\
  185. ${name} v. ${ver}${R}
  186. Usage:${b}
  187. $script ${y}-h | -l | -L | -v
  188. ${b}$script ${y}TEXT FORE [BACK] [STYLE]
  189. ${b}$script ${y}-r TEXT
  190. ${R}
  191. Options:$g
  192. BACK ${R}:${g} Name of back color for the text.
  193. FORE ${R}:${g} Name of fore color for the text.
  194. STYLE ${R}:${g} Name of style for the text.
  195. TEXT ${R}:${g} Text to colorize.
  196. -h,--help ${R}:${g} Show this message.
  197. -L,--listcodes ${R}:${g} List all colors and escape codes exported
  198. by this script.
  199. -l,--liststyles ${R}:${g} List all colors exported by this script.
  200. -r,--repr ${R}:${g} Show a representation of escape codes found
  201. in a string.
  202. This may also be used on stdin data.
  203. -v,--version ${R}:${g} Show ${b}${B}${name}${R}${g} version and exit.
  204. ${R}"
  205. }
  206. export colr
  207. export fore
  208. export back
  209. export style
  210. if [[ "$0" == "${BASH_SOURCE[0]}" ]]; then
  211. declare -a userargs
  212. do_forced=0
  213. do_list=0
  214. do_listcodes=0
  215. for arg; do
  216. case "$arg" in
  217. "-f"|"--force" )
  218. do_forced=1
  219. ;;
  220. "-h"|"--help" )
  221. print_usage ""
  222. exit 0
  223. ;;
  224. "-L"|"--listcodes" )
  225. do_listcodes=1
  226. do_list=1
  227. ;;
  228. "-l"|"--liststyles" )
  229. do_list=1
  230. ;;
  231. "-r"|"--repr" )
  232. do_repr=1
  233. ;;
  234. "-v"|"--version" )
  235. echo -e "$colr_app_name v. $colr_app_version\n"
  236. exit 0
  237. ;;
  238. -*)
  239. print_usage "Unknown flag argument: $arg"
  240. exit 1
  241. ;;
  242. *)
  243. userargs=("${userargs[@]}" "$arg")
  244. esac
  245. done
  246. # Script was executed.
  247. # Automatically disable colors if stdout is not a tty, unless forced.
  248. ((do_forced)) || colr_auto_disable 1
  249. maxwidth=7
  250. maxwidthstyle=4
  251. namefmt="%s "
  252. ((do_listcodes)) && {
  253. maxwidth=3
  254. maxwidthstyle=3
  255. namefmt="%s: "
  256. }
  257. if ((do_list)); then
  258. printf "Fore/Back"
  259. ((do_listcodes)) && printf " (fore code shown, use 48;5; for back colors)"
  260. printf ":\n"
  261. cnt=1
  262. declare -a sortednames=($(printf "%s\n" "${!fore[@]}" | sort -n))
  263. for name in "${sortednames[@]}"; do
  264. # shellcheck disable=SC2059
  265. # I am using a variable format on purpose shellcheck.
  266. printf "$namefmt" "$(colr "$(printf "%12s" "$name")" "$name")"
  267. ((do_listcodes)) && colr "$(printf "%-16s" "$(escape_code_repr "${fore[$name]}")")" "$name"
  268. ((cnt == maxwidth)) && { printf "\n"; cnt=0; }
  269. let cnt+=1
  270. done
  271. printf "\nStyles:\n"
  272. cnt=1
  273. sortednames=($(printf "%s\n" "${!style[@]}" | sort))
  274. for name in "${sortednames[@]}"; do
  275. # shellcheck disable=SC2059
  276. printf "$namefmt" "$(colr "$(printf "%12s" "$name")" "reset" "reset" "$name")"
  277. ((do_listcodes)) && colr "$(printf "%-16s" "$(escape_code_repr "${style[$name]}")")" "reset" "reset" "$name"
  278. ((cnt == maxwidthstyle)) && { printf "\n"; cnt=0; }
  279. let cnt+=1
  280. done
  281. printf "\n"
  282. elif ((do_repr)); then
  283. ((${#userargs[@]})) || {
  284. # Read lines from stdin.
  285. [[ -t 0 ]] && echo -e "\nReading from stdin until EOF (Ctrl + D)...\n"
  286. nl=$'\n'
  287. while IFS= read -r line; do
  288. # Split on spaces.
  289. userargs+=("${line}${nl}")
  290. done
  291. }
  292. ((${#userargs[@]})) || {
  293. echo -e "\nNo text to work with for --repr.\n" 1>&2
  294. exit 1
  295. }
  296. printf "%s\n" "$(escape_code_repr "${userargs[@]}")"
  297. else
  298. colr "${userargs[@]}"
  299. fi
  300. fi