diff --git a/spark b/spark index 53a38be..3d93e7b 100755 --- a/spark +++ b/spark @@ -13,6 +13,8 @@ # spark takes a comma-separated or space-separated list of data and then prints # a sparkline out of it. # +# See help for color options (inspired by Alexandre Pretto Nunes - https://github.com/apretto) +# # Examples: # # spark 1 5 22 13 53 @@ -27,8 +29,7 @@ # Generates sparklines. # # $1 - The data we'd like to graph. -_echo() -{ +_echo(){ if [ "X$1" = "X-n" ]; then shift printf "%s" "$*" @@ -37,14 +38,69 @@ _echo() fi } -spark() -{ - local n numbers= +_colorize(){ + local ticks="${1}" + local colors=(${2//,/ }) + local output="" + for i in "${!colors[@]}";do + output+="$(tput setaf ${colors[$i]})${ticks:$i:1}" + done + # reset the formatting/colors at end of the graph + output+="$(tput sgr0)" + echo "${output}" +} - # find min/max values +# The following two functions are shamelessly lifted (with minimal changes) from: +# http://unix.stackexchange.com/questions/269077/tput-setaf-color-table-how-to-determine-color-codes +# Thank you user79743! +_color_table_out(){ + for c; do + printf "$(tput setab ${c}) %03d" "${c}" + done + echo "$(tput sgr0)" +} + +_color_table(){ + local IFS=$' \t\n' + echo "" + _color_table_out {0..15} + for ((i=0;i<6;i++)); do + _color_table_out $(seq $((i*36+16)) $((i*36+51))) + done + _color_table_out {232..255} + echo "" +} + +spark(){ + # Defaults + local n data numbers= local min=0xffffffff max=0 + local invert=false + local ticks=(▁ ▂ ▃ ▄ ▅ ▆ ▇ █) + local tick_colors=(7 7 7 7 7 7 7 7) # default white - for n in ${@//,/ } + while getopts :t:p:c:n:iah opt; do + case "${opt}" in + t) local thold="${OPTARG}";; # threshold + p) local pcolor="${OPTARG}";; # color palette + c) local tcolor="${OPTARG}";; # threshold color + n) local name="${OPTARG}";; # graph display name + i) local invert=true;; # invert threshold from >= to <= + a) _color_table;exit 0;; + h) help;; + :) echo "Missing Option Argument for -${OPTARG}" >&2;exit 1;; + \?) echo "Option -${OPTARG} Unknown." >&2;exit 1;; + esac + done + shift "$((OPTIND-1))" + + # opts cleanup + [ "$1" = "--" ] && shift + if [ "$#" -eq 0 ]; then data=$(cat);else data="$@";fi + # validate data + if ! [[ "${data}" =~ ^[0-9\ ,.]*$ ]];then echo "Data not valid: ${data}";exit 1;fi + # find min/max + for n in ${data//,/ } do # on Linux (or with bash4) we could use `printf %.0f $n` here to # round the number but that doesn't work on OS X (bash3) nor does @@ -55,40 +111,120 @@ spark() numbers=$numbers${numbers:+ }$n done - # print ticks - local ticks=(▁ ▂ ▃ ▄ ▅ ▆ ▇ █) - # use a high tick if data is constant (( min == max )) && ticks=(▅ ▆) local f=$(( (($max-$min)<<8)/(${#ticks[@]}-1) )) (( f < 1 )) && f=1 + # set threshold color + if [[ -n "${tcolor}" ]];then + case "${tcolor}" in + black) tcolor=0;; + red) tcolor=1;; + green) tcolor=2;; + yellow) tcolor=3;; + blue) tcolor=4;; + magenta) tcolor=5;; + cyan) tcolor=6;; + white) tcolor=7;; + [0-9]*) ;; # if an integer, pass the int as an ansi color code to tput setaf + *) echo "Threshold color option \"${tcolor}\" unknown" >&2;exit 1;; + esac + fi + + # set color palette + if [[ -n "${pcolor}" ]];then + case "${pcolor}" in + fire) tick_colors=(228 227 226 220 214 208 202 196);; + ice) tick_colors=(20 21 27 33 39 14 255 15);; + smoke) tick_colors=(254 249 247 244 242 240 238 0);; + earth) tick_colors=(12 94 100 34 22 240 244 246);; + pride) tick_colors=(205 1 214 3 2 6 4 92);; # 8 color original design by Gilbert Baker in 1978 + lolcat) for i in {0..7};do tick_colors[${i}]=$(( RANDOM % 255 ));done;; # random colors + [0-9]*,[0-9]*,[0-9]*,[0-9]*,[0-9]*,[0-9]*,[0-9]*,[0-9]*) tick_colors=(${pcolor//,/ });; # ansi codes as a comma-seperated 8 element list + *) echo "Base color option \"${pcolor}\" unknown";exit 1;; + esac + fi + + # prepend title and min/max to graph + [[ -n "${name}" ]] && echo -n "[${name}][${min}/${max}] " + local output="" for n in $numbers do - _echo -n ${ticks[$(( ((($n-$min)<<8)/$f) ))]} + local tval=$(( ((($n-$min)<<8)/$f) )) + # test for inverted threshold color + if ${invert} && [[ -n "${thold}" ]] && [[ -n "${tcolor}" ]] && [[ "${n}" -le "${thold}" ]];then + output+="$(tput setaf ${tcolor})${ticks[${tval}]}" + # test for non-iverted threshold color + elif ! ${invert} && [[ -n "${thold}" ]] && [[ -n "${tcolor}" ]] && [[ "${n}" -ge "${thold}" ]];then + output+="$(tput setaf ${tcolor})${ticks[${tval}]}" + # test for colors without triggered threshold + elif [[ -n "${pcolor}" ]] || ( [[ -n "${thold}" ]] && [[ -n "${tcolor}" ]] );then + output+="$(tput setaf ${tick_colors[${tval}]})${ticks[${tval}]}" + # draw graph without colors + else + output+="${ticks[${tval}]}" + fi done - _echo + # if colors are used, reset the format/colors at the end of the graph + if [[ -n "${pcolor}" ]] || ( [[ -n "${thold}" ]] && [[ -n "${tcolor}" ]] );then + output+="$(tput sgr0)" + fi + _echo "${output}" } # If we're being sourced, don't worry about such things if [ "$BASH_SOURCE" == "$0" ]; then # Prints the help text for spark. - help() - { + help(){ local spark=$(basename $0) cat <|-c |-n |-p ] VALUE,... - EXAMPLES: + Options: + -n + Specify name/title of graph. Includes min/max. + + -c + Specify tick color for values exceeding the threshold. + Options: + black, red, green, yellow, blue, magenta, cyan, white + ANSI color codes (0-255) + + -t + Specify threshold (greater than or equal) as an integer. + + -i + Invert the threshold logic to less than or equal. + + -p + Specify color palette for ticks. + Options: + fire, ice, earth, smoke, pride, lolcat + Comma-seperated 8 element list ansi color codes (0-255) + eg. 1,2,3,4,55,121,73,254 + -a + Print ansi color table and exit. + + Examples: $spark 1 5 22 13 53 ▁▁▃▂█ $spark 0,30,55,80,33,150 ▁▂▃▄▂█ echo 9 13 5 17 1 | $spark ▄▆▂█▁ + $spark -p fire 1 2 3 4 5 6 7 8 + $(_colorize "▁▂▃▄▅▆▇█" "228,227,226,220,214,208,202,196") + $spark -p ice -t 6 -c green 1 3 2 4 7 1 4 2 5 6 + $(_colorize "▁▃▂▄█▁▄▂▅▆" "20,27,21,33,2,20,33,21,39,2") + $spark -p 200,205,210,215,220,225,230,235 -c 9 -t 6 -i 1,2,3,4,5,6,7,8,9 + $(_colorize "▁▁▂▃▄▅▆▇█" "9,9,9,9,9,9,225,230,235") + $spark -n "Graph Title" 1 3 5 7 10 7 5 3 1 + [Graph Title][1/10] ▁▂▄▅█▅▄▂▁ + EOF } @@ -99,5 +235,5 @@ EOF exit 0 fi - spark ${@:-`cat`} + spark "${@:-`cat`}" fi diff --git a/spark-test.sh b/spark-test.sh index 4430db2..373aa72 100644 --- a/spark-test.sh +++ b/spark-test.sh @@ -4,75 +4,175 @@ describe "spark: Generates sparklines for a set of data." spark="./spark" -it_shows_help_with_no_argv() { - $spark | grep USAGE +# Handles colorization of the "expected" graph for testing color output +_colorize(){ + local ticks="${1}" + local colors=(${2//,/ }) + local output="" + for i in "${!colors[@]}";do + output+="$(tput setaf ${colors[$i]})${ticks:$i:1}" + done + output+="$(tput sgr0)" + echo "${output}" } -it_graphs_argv_data() { +it_shows_help_with_no_argv(){ + $spark | grep Usage +} + +it_graphs_argv_data(){ graph="$($spark 1,5,22,13,5)" - test $graph = '▁▂█▅▂' + test "$graph" = '▁▂█▅▂' } -it_charts_pipe_data() { +it_charts_pipe_data(){ data="0,30,55,80,33,150" graph="$(echo $data | $spark)" test $graph = '▁▂▃▄▂█' } -it_charts_spaced_data() { +it_charts_spaced_data(){ data="0 30 55 80 33 150" graph="$($spark $data)" test $graph = '▁▂▃▄▂█' } -it_charts_way_spaced_data() { +it_charts_way_spaced_data(){ data="0 30 55 80 33 150" graph="$($spark $data)" test $graph = '▁▂▃▄▂█' } -it_handles_decimals() { +it_handles_decimals(){ data="5.5,20" graph="$($spark $data)" - test $graph = '▁█' + test "$graph" = '▁█' } -it_charts_100_lt_300() { +it_charts_100_lt_300(){ data="1,2,3,4,100,5,10,20,50,300" graph="$($spark $data)" - test $graph = '▁▁▁▁▃▁▁▁▂█' + test "$graph" = '▁▁▁▁▃▁▁▁▂█' } -it_charts_50_lt_100() { +it_charts_50_lt_100(){ data="1,50,100" graph="$($spark $data)" - test $graph = '▁▄█' + test "$graph" = '▁▄█' } -it_charts_4_lt_8() { +it_charts_4_lt_8(){ data="2,4,8" graph="$($spark $data)" - test $graph = '▁▃█' + test "$graph" = '▁▃█' } -it_charts_no_tier_0() { +it_charts_no_tier_0(){ data="1,2,3,4,5" graph="$($spark $data)" - test $graph = '▁▂▄▆█' + test "$graph" = '▁▂▄▆█' } -it_equalizes_at_midtier_on_same_data() { +it_equalizes_at_midtier_on_same_data(){ data="1,1,1,1" graph="$($spark $data)" - test $graph = '▅▅▅▅' + test "$graph" = '▅▅▅▅' +} + +it_outputs_test_name(){ + data="1,2,3,4" + graph="$($spark -n name $data)" + + test "$graph" = '[name][1/4] ▁▃▅█' +} + +it_charts_red_gt_2(){ + data="1,2,3,4,5" + graph="$(./spark -c red -t 3 $data)" + expected="$(_colorize '▁▂▄▆█' '7,7,1,1,1')" + + test "$graph" = "$expected" +} + +it_charts_red_lt_4(){ + data="1,2,3,4,5" + graph="$(./spark -c red -t 3 -i $data)" + expected="$(_colorize '▁▂▄▆█' '1,1,1,7,7')" + + test "$graph" = "$expected" +} + +it_charts_custom_ansi_color_212_gt_2(){ + data="1,2,3,4,5" + graph="$(./spark -c 212 -t 3 $data)" + expected="$(_colorize '▁▂▄▆█' '7,7,212,212,212')" + + test "$graph" = "$expected" +} + +it_charts_pride(){ + data="1,2,3,4,5,6,7,8" + graph="$(./spark -p pride $data)" + expected="$(_colorize '▁▂▃▄▅▆▇█' '205,1,214,3,2,6,4,92')" + + test "$graph" = "$expected" } + +it_charts_earth(){ + data="1,2,3,4,5,6,7,8" + graph="$(./spark -p earth $data)" + expected="$(_colorize '▁▂▃▄▅▆▇█' '12,94,100,34,22,240,244,246')" + + test "$graph" = "$expected" +} + +it_charts_pipe_data_with_opts(){ + data="1,2,3,4,5,6,7,8" + graph="$(echo "${data}" | ./spark -n "Piped Data" -c red -t 5 -p fire)" + expected="$(_colorize '▁▂▃▄▅▆▇█' '1,1,1,33,1,1,1,1')" + +} + +it_charts_ice(){ + data="1,2,3,4,5,6,7,8" + graph="$(./spark -p ice $data)" + expected="$(_colorize '▁▂▃▄▅▆▇█' '20,21,27,33,39,14,255,15')" + + test "$graph" = "$expected" +} + +it_charts_fire(){ + data="1,2,3,4,5,6,7,8" + graph="$(./spark -p fire $data)" + expected="$(_colorize '▁▂▃▄▅▆▇█' '228,227,226,220,214,208,202,196')" + + test "$graph" = "$expected" +} + +it_charts_smoke(){ + data="1,2,3,4,5,6,7,8" + graph="$(./spark -p smoke $data)" + expected="$(_colorize '▁▂▃▄▅▆▇█' '254,249,247,244,242,240,238,0')" + + test "$graph" = "$expected" +} + +it_charts_custom_ansi_palette_colors(){ + data="1,2,3,4,5,6,7,8" + graph="$(./spark -p 1,2,3,4,5,6,7,8 $data)" + expected="$(_colorize '▁▂▃▄▅▆▇█' '1,2,3,4,5,6,7,8')" + + test "$graph" = "$expected" +} + +# No lolcat test as that is an effort in frustration