|
| 1 | + |
| 2 | +# args: filed_separator, header, row_0, row_1 .. row_N |
| 3 | +# header and each row is single string of fields separted by filed separator |
| 4 | +# provided as first argument |
| 5 | +# fields can use ANSI escape code get colour output |
| 6 | +# see: https://en.wikipedia.org/wiki/ANSI_escape_code |
| 7 | +# e.g. to get green text set value: \e[38;5;76mGREEN TEXT\e[0m |
| 8 | +# header fields can have formatting options |
| 9 | +# * : group by column (note data MUST be odered by that column) |
| 10 | +# < : left align (default) |
| 11 | +# > : right align |
| 12 | +# - : TODO center align |
| 13 | +# e.g. print_table ";" "h1*<;h2;h3>" "g1;val 1,1;val 1,2" "g1;val 2,1;val 2,2" "g2;val 3,1;val 3,2" |
| 14 | +# will print: |
| 15 | +# | h1 | h2 | h3 | |
| 16 | +# |----+---------+---------| |
| 17 | +# | g1 | val 1,1 | val 1,2 | |
| 18 | +# | | val 2,1 | val 2,2 | |
| 19 | +# |----+---------+---------| |
| 20 | +# | g2 | val 3,1 | val 3,2 | |
| 21 | +function print_table() { |
| 22 | + local field_sep=${1} |
| 23 | + shift |
| 24 | + local row_count=${#} |
| 25 | + local rows=("${@}") |
| 26 | + |
| 27 | + # parse header to get initial col widths and formatting |
| 28 | + IFS=${field_sep} read -r -a header <<< "${rows[0]}" |
| 29 | + local col_count="${#header[@]}" |
| 30 | + declare -a col_width |
| 31 | + declare -a col_align |
| 32 | + local group_column=-1 |
| 33 | + local i |
| 34 | + for ((i=0;i<col_count;i++)); do |
| 35 | + local h=${header[${i}]} |
| 36 | + # get header name by stripping formatting |
| 37 | + local name=${h%%[<>*]*} |
| 38 | + # get format dierctives |
| 39 | + local frmt=${h:${#name}} |
| 40 | + header[${i}]=${name} |
| 41 | + col_width[${i}]=${#name} |
| 42 | + if [[ -n "${frmt}" ]];then |
| 43 | + if [[ ${frmt} == *'*'* ]];then |
| 44 | + group_column=${i} |
| 45 | + fi |
| 46 | + if [[ ${frmt} == *'>'* ]];then |
| 47 | + col_align[${i}]="" |
| 48 | + else |
| 49 | + col_align[${i}]="-" |
| 50 | + fi |
| 51 | + else |
| 52 | + col_align[${i}]="-" |
| 53 | + fi |
| 54 | + done |
| 55 | + |
| 56 | + # iterate over all rows to find max width for each column |
| 57 | + # also collect field data width info i.e. width of data without escape sequences |
| 58 | + # color escape |
| 59 | + local color_esc=$(printf '%b' '\e') |
| 60 | + local color_re='(.*)\\e(\[[0-9;]+m)(.*)' |
| 61 | + # for each row holds info field raw text width (after removing color escape sequnces) |
| 62 | + declare -a field_widths_rows |
| 63 | + for ((i=1;i<row_count;i++)); do |
| 64 | + IFS=${field_sep} read -r -a fields <<< "${rows[${i}]}" |
| 65 | + local row_esc= |
| 66 | + local width_row= |
| 67 | + local j |
| 68 | + for ((j=0;j<col_count;j++)); do |
| 69 | + local field_esc=${fields[${j}]} |
| 70 | + local field=${fields[${j}]} |
| 71 | + while [[ ${field} =~ ${color_re} ]]; do |
| 72 | + field=${BASH_REMATCH[1]}${BASH_REMATCH[3]} |
| 73 | + done |
| 74 | + while [[ ${field_esc} =~ ${color_re} ]]; do |
| 75 | + field_esc=${BASH_REMATCH[1]}${color_esc}${BASH_REMATCH[2]}${BASH_REMATCH[3]} |
| 76 | + done |
| 77 | + if [[ j -gt 0 ]]; then |
| 78 | + width_row="${width_row};" |
| 79 | + row_esc="${row_esc}${field_sep}" |
| 80 | + fi |
| 81 | + width_row="${width_row}${#field}" |
| 82 | + row_esc="${row_esc}${field_esc}" |
| 83 | + # keep track of column max width, take into account length witout any escape sequences |
| 84 | + if [[ ${col_width[${j}]} -lt ${#field} ]]; then |
| 85 | + col_width[${j}]=${#field} |
| 86 | + fi |
| 87 | + done |
| 88 | + rows[${i}]=${row_esc} |
| 89 | + field_widths_rows[${i}]="${width_row}" |
| 90 | + done |
| 91 | + |
| 92 | + local row_fmt="|" |
| 93 | + for ((i=0;i<col_count;i++)); do |
| 94 | + row_fmt="${row_fmt} %${col_align[${i}]}${col_width[${i}]}.${col_width[${i}]}s |" |
| 95 | + done |
| 96 | + |
| 97 | + # build divider |
| 98 | + local div="|-$(printf "%${col_width[0]}s" | tr " " "-")-" |
| 99 | + for ((i=1;i<col_count;i++)); do |
| 100 | + div="${div}+-$(printf "%${col_width[${i}]}s" | tr " " "-")-" |
| 101 | + done |
| 102 | + div="${div}|" |
| 103 | + |
| 104 | + # print header |
| 105 | + printf "${row_fmt}\n" "${header[@]}" |
| 106 | + if [[ ${group_column} -lt 0 ]];then |
| 107 | + echo "${div}" |
| 108 | + fi |
| 109 | + |
| 110 | + # use separator as uninitialized value for group as can't be valid value |
| 111 | + local current_group=${field_sep} |
| 112 | + for ((i=1;i<row_count;i++)); do |
| 113 | + IFS=${field_sep} read -r -a fields <<< "${rows[${i}]}" |
| 114 | + IFS=';' read -r -a field_widths <<< "${field_widths_rows[${i}]}" |
| 115 | + |
| 116 | + if [[ ${group_column} -ge 0 ]];then |
| 117 | + if [[ ${fields[${group_column}]} != ${current_group} ]];then |
| 118 | + echo "${div}" |
| 119 | + current_group=${fields[${group_column}]} |
| 120 | + else |
| 121 | + fields[${group_column}]="" |
| 122 | + fi |
| 123 | + fi |
| 124 | + |
| 125 | + row_fmt="|" |
| 126 | + for ((j=0;j<col_count;j++)); do |
| 127 | + local width |
| 128 | + if [[ ${j} -eq ${group_column} && ${fields[${group_column}]} == "" ]]; then |
| 129 | + width=${col_width[${j}]} |
| 130 | + else |
| 131 | + width=${col_width[${j}]} |
| 132 | + local field_esc_width=${#fields[${j}]} |
| 133 | + local field_width=${field_widths[${j}]} |
| 134 | + if [[ ${field_esc_width} != ${field_width} ]];then |
| 135 | + width=$((field_esc_width+width-field_width)) |
| 136 | + fi |
| 137 | + fi |
| 138 | + row_fmt="${row_fmt} %${col_align[${j}]}${width}.${width}s |" |
| 139 | + done |
| 140 | + printf "${row_fmt}\n" "${fields[@]}" |
| 141 | + done |
| 142 | +} |
0 commit comments