├── LICENSE ├── README.md ├── examples └── ansi-demo.sh ├── lib └── ansi.sh └── test └── ansi.bats /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Marc 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ANSI Bash Function Library 2 | ========================== 3 | 4 | Bash functions for Terminals with ANSI support. 5 | 6 | Features 7 | -------- 8 | 9 | * Coloured text 10 | * Titles and headings 11 | * Text alignment and formatting 12 | * Strip ANSI escape sequences 13 | * Color conversions and aliases 14 | 15 | How it looks? 16 | ------------- 17 | 18 | Here some screenshots from the demo script in the examples folder. 19 | 20 | ![Colors demo](https://raw.githubusercontent.com/mfornos/ansi-bfl/screenshots/colors-demo.jpg "Colors demo") 21 | 22 | ![Report demo](https://raw.githubusercontent.com/mfornos/ansi-bfl/screenshots/report-demo.jpg "Report demo") 23 | 24 | How it feels? 25 | ------------- 26 | 27 | __Coloured text__ 28 | 29 | ```bash 30 | ansi::fg red 31 | echo Hello! 32 | ansi::reset 33 | ``` 34 | 35 | ```bash 36 | echo "$(ansi::fg red)Hello!" 37 | ``` 38 | 39 | ```bash 40 | ansi::fg 2 41 | echo Hello! 42 | ``` 43 | 44 | __Headers__ 45 | 46 | ```bash 47 | ansi::heading "Heading 1" 48 | ``` 49 | 50 | ```bash 51 | ansi::title "Title 1" "*" 52 | ``` 53 | 54 | __Aligned text__ 55 | 56 | ```bash 57 | ansi::right "LOREM IPSUM" 58 | ``` 59 | 60 | ```bash 61 | ansi::left_right "Summary" "0.10 $(ansi::fg blue)YES$(ansi::reset)" 80 62 | ``` 63 | 64 | How it works? 65 | ------------- 66 | 67 | On most typical terminals and operating systems *ANSI Bash Function Library* should work out of the box, if you don't see any colors or the output is not exactly what you expect, you probably have a terminal type that doesn't handle all the ANSI terminal codes used by **ansi-bfl**. 68 | 69 | On Linux, FreeBSD or OpenBSD you could try to set 70 | 71 | ```sh 72 | TERM=xterm-256color 73 | ``` 74 | 75 | On AIX and Solaris you could try with 76 | 77 | ```sh 78 | TERM=dtterm 79 | ``` 80 | 81 | Reading the *terminfo*, *termcap* and *term* manual pages can help you to find out which terminal type should be set. 82 | 83 | // EOF 84 | -------------------------------------------------------------------------------- /examples/ansi-demo.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # 3 | # ANSI lib demo. 4 | # 5 | 6 | . ../lib/ansi.sh 7 | 8 | BASIC=(ansi::bold ansi::half_bright ansi::italics ansi::underline ansi::reverse) 9 | 10 | function demo::title { 11 | echo; ansi::title "$1"; echo; 12 | } 13 | 14 | function demo::pt { 15 | ${1}; echo -en "$2"; ansi::reset; echo -n " " 16 | } 17 | 18 | function demo::cube { 19 | demo::title "8-bit Color Cube" 20 | 21 | local green red blue 22 | 23 | for green in {0..5}; do 24 | for red in {0..5}; do 25 | for blue in {0..5}; do 26 | let color="16 + ($red * 36) + ($green * 6) + $blue" 27 | ansi::bg $color 28 | echo -n " " 29 | done; 30 | ansi::reset 31 | done; 32 | echo 33 | done; 34 | } 35 | 36 | function demo::text { 37 | demo::title "Basic" 38 | 39 | for TT in ${BASIC[@]}; do 40 | demo::pt $TT "$TT" 41 | done 42 | echo 43 | 44 | demo::title "Colors" 45 | 46 | for SC in ${SYS_COLORS[@]}; do 47 | demo::pt "ansi::fg $SC" "$SC" 48 | done 49 | 50 | for SC in ${SYS_COLORS[@]}; do 51 | demo::pt "ansi::bg $SC" "$SC" 52 | done 53 | echo 54 | 55 | demo::title "Bright Colors" 56 | 57 | for SC in ${SYS_COLORS[@]}; do 58 | bright="bright_$SC" 59 | demo::pt "ansi::fg $bright" "b_$SC" 60 | done 61 | 62 | for SC in ${SYS_COLORS[@]}; do 63 | bright="bright_$SC" 64 | demo::pt "ansi::bg $bright" "b_$SC" 65 | done 66 | echo 67 | } 68 | 69 | function demo::banner { 70 | ansi::clear 71 | l=$(ansi::hline $COLUMNS '*') 72 | ansi::hcenter_print "$l" 0 73 | ansi::hcenter_print "$(ansi::reverse)- ANSI Demo -$(ansi::reset)" 1 74 | ansi::hcenter_print "$l" 2 75 | ansi::reset 76 | } 77 | 78 | function demo::success { 79 | ansi::left_right "$1" "[ $(ansi::fg green)SUCCESS$(ansi::reset) ]" 80 '.' 80 | } 81 | 82 | function demo::fail { 83 | ansi::left_right "$1" "[ $(ansi::fg red)FAIL$(ansi::reset) ]" 80 '.' 84 | } 85 | 86 | function demo::report { 87 | ansi::right "[TEXT]" 88 | star="$(ansi::fg bright_red)\xe2\x98\x85$(ansi::reset)" 89 | demo::title "Cute Report" 90 | ansi::heading "$star AWESOME Report" 80 "$star" 91 | echo 92 | demo::success "How cute is this kitten?" 93 | demo::fail "Help needed determining sex of kitten" 94 | demo::success "Everything about kittens" 95 | ansi::hnline 80 '=' 96 | ansi::left_right "$(ansi::bold)Summary$(ansi::reset)" "0.10 $(ansi::fg blue)YES$(ansi::reset)" 80 97 | } 98 | 99 | function demo { 100 | ansi::tput_installed || { echo "tput not found!" && exit -1; } 101 | ansi::colors_supported || echo "WARN: Your term supports less than 256 colors ($TERM)." 102 | 103 | demo::banner 104 | 105 | ansi::right "[COLORS]" 106 | demo::text 107 | demo::title "Rainbow" 108 | echo -e $(ansi::rainbow "Somewhere over the rainbow.") 109 | 110 | demo::cube 111 | 112 | demo::report 113 | 114 | ansi::right "[EOD]" 115 | } 116 | 117 | # Kick-off 118 | demo 119 | 120 | exit 0 121 | 122 | -------------------------------------------------------------------------------- /lib/ansi.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # 3 | # Bash functions for Terminals with ANSI support. 4 | # 5 | 6 | # ==================================================== 7 | # System colors 8 | # ==================================================== 9 | black=0; bright_black=8 10 | red=1; bright_red=9 11 | green=2; bright_green=10 12 | yellow=3; bright_yellow=11 13 | blue=4; bright_blue=12 14 | magenta=5; bright_magenta=13 15 | cyan=6; bright_cyan=14 16 | white=7; bright_white=15 17 | 18 | SYS_COLORS=(black red green yellow blue magenta cyan white) 19 | 20 | 21 | # ==================================================== 22 | # ANSI lib 23 | # ==================================================== 24 | 25 | ## 26 | # Checks the supported number of colors 27 | # 28 | # Arguments 29 | # - 1: ncolors (opt) 30 | # num. of colors required, defaults to 256 31 | # 32 | # Return 33 | # 0 if ncolors are supported, -1 otherwise 34 | # 35 | function ansi::colors_supported { 36 | local ncolors=$(tput colors) 37 | if [ "$ncolors" -lt ${1:-256} ]; then return -1; fi 38 | return 0 39 | } 40 | 41 | ## 42 | # Checks if tput is installed 43 | # 44 | # Return 45 | # 0 if tput is found, -1 otherwise 46 | # 47 | function ansi::tput_installed { 48 | command -v tput >/dev/null 2>&1 || return -1; 49 | return 0 50 | } 51 | 52 | ## 53 | # Prints an horizontal line 54 | # 55 | # Arguments 56 | # - 1: line length (opt) 57 | # defaults to current terminal columns 58 | # - 2: separator (opt) 59 | # supports unicode and ansi, defaults to '-' 60 | # 61 | function ansi::hline { 62 | local hl="$(printf '%*s' ${1:-$COLUMNS} '')" 63 | echo -ne "${hl// /${2:--}}" 64 | } 65 | 66 | 67 | ## 68 | # Prints an horizontal line with carriage return at the end 69 | # 70 | # Arguments 71 | # - 1: line length (opt) 72 | # defaults to current terminal columns 73 | # - 2: separator (opt) 74 | # supports unicode and ansi, defaults to '-' 75 | # 76 | function ansi::hnline { 77 | ansi::hline "$@"; echo; 78 | } 79 | 80 | ## 81 | # Prints a text surrounded by horizontal lines 82 | # 83 | # Arguments 84 | # - 1: text 85 | # - 2: line length (opt) 86 | # defaults to current terminal columns 87 | # - 3: separator (opt) 88 | # supports unicode and ansi, defaults to '-' 89 | # 90 | function ansi::heading { 91 | ansi::hnline $2 "$3"; 92 | echo -e "$1" 93 | ansi::hnline $2 "$3"; 94 | } 95 | 96 | ## 97 | # Prints a text followed by a line of the same length 98 | # 99 | # Arguments 100 | # - 1: text 101 | # - 2: separator (opt) 102 | # supports unicode and ansi, defaults to '-' 103 | # - 3: line length (opt) 104 | # defaults to text length 105 | # 106 | function ansi::title { 107 | echo -e "$1" 108 | ansi::hnline ${3:-${#1}} "${2:--}" 109 | } 110 | 111 | ## 112 | # Strips ANSI escape sequences 113 | # 114 | # Arguments 115 | # - 1: text 116 | # 117 | # Return 118 | # text without ANSI escapes 119 | # 120 | function ansi::strip { 121 | local tmp="$1" 122 | local esc=$(printf "\x1b") 123 | local tpa=$(printf "\x28") 124 | local re="(.*)$esc[\[$tpa][0-9]*;*[mKB](.*)" 125 | while [[ $tmp =~ $re ]]; do 126 | tmp=${BASH_REMATCH[1]}${BASH_REMATCH[2]} 127 | done 128 | echo "$tmp" 129 | } 130 | 131 | # 132 | # Text alignment 133 | # ---------------------------------------------------- 134 | COLUMNS=$(tput cols) 135 | LINES=$(tput lines) 136 | VCENTER=$(( $LINES / 2 )) 137 | 138 | ## 139 | # Moves the cursor 140 | # 141 | # Arguments 142 | # - 1: text offset 143 | # text to be counted as horizontal offset 144 | # - 2: column coordinate (opt) 145 | # defaults to vertical center 146 | # - 3_ line coordinate (opt) 147 | # defaults to horizontal center, according to text 148 | # 149 | function ansi::cursor_to { 150 | local x=${2:-$VCENTER} 151 | local y=${3:-$(( ($COLUMNS - ${#1}) / 2 ))} 152 | tput cup $x $y 153 | } 154 | 155 | ## 156 | # Centers cursor horizontally 157 | # 158 | # Arguments 159 | # - 1: text offset 160 | # text to be counted as horizontal offset 161 | # - 2: column coordinate 162 | # 163 | function ansi::hcenter { 164 | local txt=$(ansi::strip "$1") 165 | local y=$(( ($COLUMNS - ${#txt}) / 2 )) 166 | tput cup $2 $y 167 | } 168 | 169 | ## 170 | # Centers cursor horizontally and prints the given text 171 | # 172 | # Arguments 173 | # - 1: text 174 | # text to be counted as horizontal offset 175 | # - 2: column coordinate 176 | # 177 | function ansi::hcenter_print { 178 | ansi::hcenter "$1" $2 179 | echo -e "$1" 180 | } 181 | 182 | ## 183 | # Prints a line connecting between two symmetrically aligned 184 | # texts 185 | # 186 | # Arguments 187 | # - 1: left text 188 | # text to be counted as offset 189 | # - 2: right text 190 | # text to be counted as offset 191 | # - 3: line length (opt) 192 | # defaults to current terminal columns 193 | # - 4: separator (opt) 194 | # supports unicode and ansi, defaults to space char 195 | # 196 | function ansi::lrl { 197 | local y=$(( ${3:-$COLUMNS} - (${#1} + ${#2}) )) 198 | ansi::hline $y "${4:- }" 199 | } 200 | 201 | ## 202 | # Prints two symmetrically aligned texts 203 | # 204 | # Arguments 205 | # - 1: left text 206 | # text to be counted as offset 207 | # - 2: right text 208 | # text to be counted as offset 209 | # - 3: line length (opt) 210 | # defaults to current terminal columns 211 | # - 4: separator (opt) 212 | # supports unicode and ansi, defaults to space char 213 | # 214 | function ansi::left_right { 215 | local left=$(ansi::strip "$1") 216 | local right=$(ansi::strip "$2") 217 | echo -en "$1" 218 | ansi::lrl "$left" "$right" ${3:-$COLUMNS} "${4:- }" 219 | echo -e "$2" 220 | } 221 | 222 | function ansi::right { 223 | ansi::left_right '' "$1" 224 | } 225 | 226 | # 227 | # Color utils 228 | # ---------------------------------------------------- 229 | CF=6/256 230 | RAINBOW_COLORS=(red yellow green blue magenta) 231 | 232 | ## 233 | # Converts an RGB color to its closest 8-bit equivalent 234 | # 235 | # Arguments 236 | # - 1: red 237 | # 0-255 number 238 | # - 2: green 239 | # 0-255 number 240 | # - 3: blue 241 | # 0-255 number 242 | # 243 | # Return 244 | # 0-255 value for its 8-bit representation 245 | # 246 | function ansi::rgb_conv { 247 | echo $(( 16 + (($1 * $CF) * 36) + (($2 * $CF) * 6) + ($3 * $CF) )) 248 | } 249 | 250 | ## 251 | # Resolves an ANSI color literal, if needed 252 | # 253 | # Arguments 254 | # - 1: color 255 | # 0-255 number or literal name 256 | # 257 | function ansi::color { 258 | local num=$1 259 | local re='^[0-9]+$' 260 | if ! [[ $num =~ $re ]]; then num="${!1}"; fi 261 | echo $num 262 | } 263 | 264 | ## 265 | # Fluffy rainbow printing 266 | # 267 | # Arguments 268 | # - 1: text 269 | # 270 | function ansi::rainbow { 271 | local str='' 272 | for (( i=0; i<${#1}; i++ )); do 273 | ch=${1:$i:1} 274 | co=${RAINBOW_COLORS[$((${i}%${#RAINBOW_COLORS[*]}))]} 275 | str+="$(ansi::fg $co)$ch$(ansi::reset)" 276 | done 277 | echo -en "$str" 278 | } 279 | 280 | # ==================================================== 281 | # Tput sugar - Terminfo commands and modes 282 | # ==================================================== 283 | 284 | ## 285 | # Sets a foreground color. 286 | # 287 | # For system colors you can use the color names. 288 | # v.g. 'fg red' 289 | # 290 | # Arguments 291 | # - 1: color 292 | # 0-255 number or literal name 293 | # 294 | function ansi::fg { 295 | tput "setaf" $(ansi::color $1) 296 | } 297 | 298 | ## 299 | # Sets a background color 300 | # 301 | # Arguments 302 | # - 1: color 303 | # 0-255 number or literal name 304 | # 305 | function ansi::bg { 306 | tput "setab" $(ansi::color $1) 307 | } 308 | 309 | ## 310 | # Enables bold text 311 | # 312 | function ansi::bold { 313 | tput "bold" 314 | } 315 | 316 | ## 317 | # Enables blinking text 318 | # 319 | function ansi::blink { 320 | tput "blink" 321 | } 322 | 323 | ## 324 | # Enables reverse video mode 325 | # 326 | function ansi::reverse { 327 | tput "rev" 328 | } 329 | 330 | ## 331 | # Enables underline 332 | # 333 | function ansi::underline { 334 | tput "smul" 335 | } 336 | 337 | ## 338 | # Disables underline 339 | # 340 | function ansi::end_underline { 341 | tput "rmul" 342 | } 343 | 344 | ## 345 | # Enables half bright mode 346 | # 347 | function ansi::half_bright { 348 | tput "dim" 349 | } 350 | 351 | ## 352 | # Enables italics 353 | # 354 | function ansi::italics { 355 | tput "sitm" 356 | } 357 | 358 | ## 359 | # Disables italics 360 | # 361 | function ansi::end_italics { 362 | tput "ritm" 363 | } 364 | 365 | ## 366 | # Triggers terminal bell 367 | # 368 | function ansi::bell { 369 | tput "bel" 370 | } 371 | 372 | ## 373 | # Flashes the terminal visual bell 374 | # 375 | function ansi::flash { 376 | tput "flash" 377 | } 378 | 379 | ## 380 | # Clears the terminal screen 381 | # 382 | function ansi::clear { 383 | tput "clear" 384 | } 385 | 386 | ## 387 | # Resets all term modes. 388 | # Typically used to end previous escape sequences. 389 | # v.g. 390 | # 'ansi::fg 230; echo hi!; ansi::reset; echo ho!;' 391 | # 'echo "$(ansi::fg 230)hi!$(ansi::reset) ho!"' 392 | # 393 | function ansi::reset { 394 | tput "sgr0" 395 | } 396 | 397 | # EOF 398 | -------------------------------------------------------------------------------- /test/ansi.bats: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bats 2 | 3 | load $BATS_TEST_DIRNAME/../lib/ansi.sh 4 | 5 | @test "strip ANSI" { 6 | txt="$(ansi::fg 2)Hi$(ansi::reset)" 7 | result="$(ansi::strip $txt)" 8 | [ ${#txt} -eq 13 ] 9 | [ ${#result} -eq 2 ] 10 | } 11 | 12 | @test "color names" { 13 | green="$(ansi::color green)" 14 | cyan="$(ansi::color 6)" 15 | [ $green -eq 2 ] 16 | [ $cyan -eq 6 ] 17 | } 18 | 19 | @test "rgb convert" { 20 | c="$(ansi::rgb_conv 255 10 0)" 21 | [ $c -eq 196 ] 22 | } 23 | 24 | @test "horizontal line" { 25 | result="$(ansi::hline 5 '-')" 26 | [ "$result" == '-----' ] 27 | } 28 | 29 | @test "horizontal new line" { 30 | result="$(ansi::hnline 5 '-' | tr '\n' 'N')" 31 | [ "$result" == '-----N' ] 32 | } 33 | 34 | @test "heading" { 35 | result="$(ansi::heading 'hi ho!' | tr '\n' 'N')" 36 | [[ "$result" =~ "Nhi ho!N" ]] 37 | } 38 | 39 | @test "title" { 40 | result="$(ansi::title 'hi ho!' | tr '\n' 'N')" 41 | [ "$result" == "hi ho!N------N" ] 42 | } 43 | 44 | # EOF 45 | --------------------------------------------------------------------------------