new tool: args2env
[hband-tools.git] / user-tools / tabularize
blob713cb33c6e6e8f075726676633459f8b9351271d
1 #!/bin/bash
3 true <<EOF
4 =pod
6 =head1 NAME
8 tabularize - Takes TAB-delimited lines of text and outputs formatted table.
10 =head1 SYNOPSIS
12 I<COMMAND> | tabularize [I<OPTIONS>]
14 =head1 OPTIONS
16 =over 4
18 =item -a, --ascii
20 7-bit ascii borders
22 =item -u, --unicode
24 borders with nice graphical chars
26 =item -H, --no-horizontal
28 no horizontal lines in the output
30 =item -M, --no-margins
32 no margins, ie. no right-most and left-most vertical borders
34 =item -v, --output-vertical-separator I<CHAR>
36 vertical separator character(s) in the output
38 =item -r, --align-right I<NUM>
40 align these columns (0-indexed) to the right,
41 others are auto-detected and if deemed to contain numeric data,
42 only then aligned to the right.
43 this option is repeatable.
45 =item -l, --align-left I<NUM>
47 similar to --align-right option
49 =back
51 =head1 ENVIRONMENT
53 =over 4
55 =item PAGER
57 If B<$PAGER> is set and standard output is a terminal
58 and the resulting table is wider than the terminal,
59 then pipe the table through B<$PAGER>.
61 =back
63 =cut
65 EOF
68 TAB=$'\t'
69 IFS=''
71 set_ascii()
73 verticalBar='|'
74 horizontalBar='-'
75 topleftCorner='+'
76 toprightCorner='+'
77 bottomleftCorner='+'
78 bottomrightCorner='+'
79 leftCross='+'
80 middleCross='+'
81 rightCross='+'
82 topCross='+'
83 bottomCross='+'
85 set_unicode()
87 verticalBar='│'
88 horizontalBar='─'
89 topleftCorner='┌'
90 toprightCorner='┐'
91 bottomleftCorner='└'
92 bottomrightCorner='┘'
93 leftCross='├'
94 middleCross='┼'
95 rightCross='┤'
96 topCross='┬'
97 bottomCross='┴'
101 set_unicode
103 declare -a columnAlignment=()
104 horizontalLines=yes
105 margins=yes
107 while [ $# -gt 0 ]
109 case "$1" in
110 -a|--ascii)
111 set_ascii
113 -u|--unicode)
114 set_unicode
116 -H|--no-horizontal)
117 horizontalLines=''
119 -M|--no-margins)
120 margins=''
122 -r|--align-right)
123 shift
124 columnAlignment[$1]=right
126 -l|--align-left)
127 shift
128 columnAlignment[$1]=left
130 -v|--output-vertical-separator)
131 shift
132 verticalBar=$1
133 topleftCorner=$1
134 toprightCorner=$1
135 bottomleftCorner=$1
136 bottomrightCorner=$1
137 leftCross=$1
138 middleCross=$1
139 rightCross=$1
140 topCross=$1
141 bottomCross=$1
143 -h|--help)
144 pod2text "$0"
145 exit
148 echo "$0: unknown option: $1" >&2
149 exit -1
151 esac
152 shift
153 done
156 explode()
158 local separator=$1
159 local string=$2
160 local new_string
161 declare -g -a EXPLODE_ARRAY=()
163 while [ -n "$string" ]
165 EXPLODE_ARRAY+=("${string%%$separator*}")
166 new_string=${string#*$separator}
167 if [ "$string" = "$new_string" ]
168 then
169 break
171 string=$new_string
172 done
174 # return $EXPLODE_ARRAY
177 is_numeric_check_sep()
179 local thousands_sep=$2
180 local fraction_sep=$3
181 [[ $1 =~ ^[+-]?[0-9]+($thousands_sep[0-9]+)*($fraction_sep[0-9]+($thousands_sep[0-9]+)*)?$ ]]
183 is_numeric()
185 is_numeric_check_sep "$1" ',' . && return 0
186 is_numeric_check_sep "$1" ' ' , && return 0
187 is_numeric_check_sep "$1" '.' , && return 0
188 return 1
192 # compute width of each column
194 column_width=()
195 num_line=0
196 text=''
197 numericals_by_col=()
198 non_numericals_by_col=()
199 while read -r line
201 text="$text${text:+$'\n'}$line"
202 explode $'\t' "$line"
204 column=0
205 for cell in "${EXPLODE_ARRAY[@]}"
207 width=${#cell}
208 if [ -z "${column_width[$column]}" ] || [ $width -gt "${column_width[$column]}" ]
209 then
210 column_width[$column]=$width
213 # guess if this cell has numerical content (skip 1st line as it's likely a header)
214 if [ $num_line -gt 0 -a -n "$cell" -a -z "${columnAlignment[$column]:-}" ]
215 then
216 if is_numeric "$cell"
217 then
218 let numericals_by_col[$column]++
219 else
220 let non_numericals_by_col[$column]++
223 let column++
224 done
226 let num_line++
227 done
229 if [ $num_line = 0 ]
230 then
231 # no input, no output
232 exit 0
236 set -u
238 # compose the format string
240 table_width=0
241 cellFmt=''
242 topFmt=''
243 innerFmt=''
244 bottomFmt=''
245 segments=()
246 sequentColumn=''
247 for column in "${!column_width[@]}"
249 if [ -z "${columnAlignment[$column]:-}" ]
250 then
251 if [ ${numericals_by_col[$column]:-0} -ge ${non_numericals_by_col[$column]:-0} ]
252 then
253 columnAlignment[$column]=right
254 else
255 columnAlignment[$column]=left
259 table_width=$[table_width + (column == 0 ? (0${margins:+1} ? ${#verticalBar} : 0) : ${#verticalBar}) + ${column_width[$column]}]
261 cellFmt="${cellFmt:-${margins:+$verticalBar}}${sequentColumn:+$verticalBar}%*s"
262 topFmt="${topFmt:-${margins:+$topleftCorner}}${sequentColumn:+$topCross}%${column_width[$column]}s"
263 innerFmt="${innerFmt:-${margins:+$leftCross}}${sequentColumn:+$middleCross}%${column_width[$column]}s"
264 bottomFmt="${bottomFmt:-${margins:+$bottomleftCorner}}${sequentColumn:+$bottomCross}%${column_width[$column]}s"
265 segments+=('')
267 sequentColumn=yes
268 done
269 table_width=$[table_width + (0${margins:+1} ? ${#verticalBar} : 0)]
270 cellFmt="$cellFmt${margins:+$verticalBar}"
271 topFmt="$topFmt${margins:+$toprightCorner}"
272 innerFmt="$innerFmt${margins:+$rightCross}"
273 bottomFmt="$bottomFmt${margins:+$bottomrightCorner}"
276 topGrid=''
277 innerGrid=''
278 bottomGrid=''
279 if [ $horizontalLines ]
280 then
281 topGrid=`printf -- "$topFmt" "${segments[@]}"`
282 topGrid=${topGrid// /$horizontalBar}$'\n'
283 innerGrid=`printf -- "$innerFmt" "${segments[@]}"`
284 innerGrid=${innerGrid// /$horizontalBar}$'\n'
285 bottomGrid=`printf -- "$bottomFmt" "${segments[@]}"`
286 bottomGrid=${bottomGrid// /$horizontalBar}$'\n'
290 print_table_g()
292 line_no=1
293 while read -r line
295 if [ $line_no = 1 ]
296 then
297 echo -n "$topGrid"
298 else
299 echo -n "$innerGrid"
302 explode $'\t' "$line"
303 printf_args=()
304 for column in "${!column_width[@]}"
306 [ $column -lt ${#EXPLODE_ARRAY[@]} ] && cell=${EXPLODE_ARRAY[$column]} || cell=''
307 chars=${#cell}
308 bytes=`LANG=C; echo "${#cell}"`
309 swidth=$[ ${column_width[$column]} + ( $bytes - $chars ) ]
310 [ ${columnAlignment[$column]} = left ] && swidth=-$swidth
311 printf_args+=($swidth "$cell")
312 done
313 printf -- "$cellFmt\n" "${printf_args[@]}"
315 let line_no++
316 done <<<"$text"
318 echo -n "$bottomGrid"
322 if [ -t 1 -a -n "$PAGER" ]
323 then
324 terminal_cols=`tput cols`
325 if [ "${terminal_cols:-0}" -le $table_width ]
326 then
327 print_table_g | exec "$PAGER"
328 exit ${PIPESTATUS[1]}
332 print_table_g