ste.in (13006B)
1 #!/bin/sh 2 # vim: set ft=bash: 3 # ste - Simple Table Editor 4 # This program is licensed under the terms of GNU GPL v3 or (at your option) 5 # any later version. Copyright (C) 2023-2026 Страхиња Радић. 6 # See the file LICENSE for exact copyright and license details. 7 8 # Global options 9 # Clear screen after update 10 clear=1 11 # Table output width in screen columns (0 = detect) 12 cols=0 13 # History file location (if using rlwrap) 14 historyfile="$HOME/.ste_history" 15 # Table output height in screen rows (0 = detect) 16 rows=0 17 # Pass -m to table 18 msdos=0 19 # Pass -n to table (recommended) 20 noansi=1 21 22 # Which flavors of TSV/CSV utilities to use 23 delprog=tsvdel 24 editprog=tsvedit 25 findprog=tsvfind 26 insprog=tsvins 27 moveprog=tsvmove 28 selprog=tsvselect 29 tableprog=tsvtable 30 31 # Variables which normally should not be modified by user 32 DATE="%DATE%" 33 VERSION="%VERSION%" 34 editfilter='' 35 if command -v vipe >/dev/null 2>&1; then 36 editfilter="vipe --suffix tsv" 37 fi 38 39 error() 40 { 41 fmt=$1 42 shift 43 # shellcheck disable=SC2059 44 printf "%s: %s\n" "$prog" "$(printf "$fmt" "$@")" 45 } 46 47 detectcols() 48 { 49 stty -a | tr \; '\n' | grep -E 'colu?m?n?s' | tr -d 'columns ' 50 } 51 52 detectrows() 53 { 54 stty -a | tr \; '\n' | grep rows | tr -d 'rows ' 55 } 56 57 help() 58 { 59 case "$1" in 60 /) printf "/ text - Search for text\n" 61 printf "/ colno text - Search for text in column colno\n";; 62 =) printf "= - Show current row\n";; 63 cols) printf "cols amount - Set screen width in columns to amount\n" 64 printf " (0 = autodetect)\n" 65 printf "cols - Show the current screen width\n";; 66 delete|x) 67 printf "delete | x - Delete the current table row\n";; 68 edit|e) printf "edit | e - Edit the current table row\n";; 69 exit|q) printf "exit | q - Exit %s\n" "$prog";; 70 G) printf "G - Set current row to last\n";; 71 g) printf "g num - Set current table row to num (header = 0)\n" 72 printf "g - Set current row to header row (0)\n";; 73 help|h) printf "help [command] | h [command] - Show help " 74 printf "(about a command)\n";; 75 insert|i) 76 printf "insert - | i - - Add a row to the table before the " 77 printf "current line\n" 78 printf "insert + | i + - Add a row to the table after the " 79 printf "current line\n" 80 printf "insert | i - Add a row to the end of table\n";; 81 J) printf "J num - Move the current row down num rows\n" 82 printf "J - Move the current row down\n";; 83 j) printf "j num - Move the current row pointer down num rows\n" 84 printf "j - Move the current row pointer down\n";; 85 K) printf "K num - Move the current row up num rows\n" 86 printf "K - Move the current row up\n";; 87 k) printf "k num - Move the current row pointer up num rows\n" 88 printf "k - Move the current row pointer up\n";; 89 n) printf "n - Find next occurence of previously searched text\n";; 90 print|p) 91 printf "print | p - Print (redraw) the table\n";; 92 rows) printf "rows amount - Set screen height in lines\n" 93 printf " (0 = autodetect)\n" 94 printf "rows - Show the current screen height\n";; 95 set) printf "set [no]option - Turn option on/off\n" 96 printf "set - Show option states\n";; 97 "") printf "Commands: / = cols delete e edit exit G g h help i " 98 printf "insert J j K k n p print q rows set x\n" 99 printf "help [command] - Show help (about a command)\n";; 100 *) error "Unknown command '%s'" "$args";; 101 esac 102 } 103 104 printopt() 105 { 106 opt=$1 107 if [ "$(eval echo "\$$opt")" -eq 0 ]; then 108 printf "no" 109 fi 110 printf "%s\n" "$opt" 111 } 112 113 setopts() 114 { 115 opts='' 116 opts="$opts -c $_cols" 117 if [ "$msdos" -eq 1 ]; then 118 opts="$opts -m" 119 fi 120 if [ "$noansi" -eq 1 ]; then 121 opts="$opts -n" 122 fi 123 printf "%s" "$opts" 124 } 125 126 usage() 127 { 128 cat <<EOU 129 Usage: ${prog} [-hVv] 130 ${prog} [tsvfile.tsv] 131 EOU 132 } 133 134 version() 135 { 136 printf "%s %s, committed on %s\n" \ 137 "${prog}" "${VERSION}" "${DATE}" 138 } 139 140 file=$1 141 prog=${0##*/} 142 143 OPTIND=1 144 while getopts hVv OPT >/dev/null 2>&1 145 do 146 case $OPT in 147 h) usage 148 exit 149 ;; 150 V) version 151 cat <<EOC 152 This program is licensed under the terms of GNU GPL v3 or (at your option) 153 any later version. Copyright (C) 2023-2026 Страхиња Радић. 154 See the file LICENSE for exact copyright and license details. 155 156 Global options: 157 clear: ${clear} 158 cols: ${cols} 159 historyfile: ${historyfile} 160 rows: ${rows} 161 msdos: ${msdos} 162 noansi: ${noansi} 163 EOC 164 exit 165 ;; 166 v) version 167 exit 168 ;; 169 170 ?) error "Unknown parameter '%s'" "$1" 171 usage >&2 172 exit 1 173 ;; 174 esac 175 OPTARG= 176 done 177 178 if [ $# -lt 1 ]; then 179 usage >&2 180 exit 1 181 fi 182 183 # shellcheck disable=SC2065 184 if test ! -r "$file" >/dev/null 2>&1; then 185 if [ "$editfilter" ]; then 186 printf '# New file - enter column headers, one per line\n\n' | 187 ${editfilter} | ${insprog} "$file" 188 else 189 #error "warning: vipe not detected" 190 printf '# New file - enter column headers, one per line\n' 191 cat | ${insprog} "$file" 192 fi 193 fi 194 # shellcheck disable=SC2065 195 if test ! -r "$file" >/dev/null 2>&1; then 196 error "File does not exist: '%s'" "$file" 197 exit 1 198 fi 199 200 running=1 201 donevipewarn=0 202 currow=0 203 firstrow=0 204 nextcmd='' 205 oldcols='' 206 oldrows='' 207 redraw=1 208 sgi=$(printf "%b" "\033[") 209 210 maxcols=$(sed 1q "$file" | tr -dc '\t' | wc -c | awk '{print $1+1}') 211 while [ "$running" -eq 1 ]; do 212 maxrows=$(wc -l "$file" | awk '{print $1}') 213 _cols="$cols" 214 if [ "$cols" -eq 0 ]; then 215 _cols=$(detectcols) 216 fi 217 if [ -z "$oldcols" ]; then 218 oldcols=$_cols 219 fi 220 _rows="$rows" 221 if [ "$rows" -eq 0 ]; then 222 _rows=$(detectrows) 223 fi 224 if [ -z "$oldrows" ]; then 225 oldrows=$_rows 226 fi 227 228 if [ "$oldrows" -ne "$_rows" ] || [ "$oldcols" -ne "$_cols" ]; then 229 redraw=1 230 oldrows=$_rows 231 oldcols=$_cols 232 fi 233 234 if [ "$currow" -lt "$firstrow" ]; then 235 firstrow="$currow" 236 redraw=1 237 elif [ $((currow - firstrow)) -gt $((_rows - 4)) ]; then 238 firstrow=$((currow - _rows + 4)) 239 if [ "$firstrow" -lt 0 ]; then 240 firstrow="$currow" 241 fi 242 redraw=1 243 fi 244 if [ "$redraw" -eq 1 ]; then 245 if [ "$clear" -eq 1 ]; then 246 clear 247 fi 248 firstrowcols=$(tail -n+$((firstrow+1)) "$file" | sed 1q | 249 tr -dc '\t' | wc -c | awk '{print $1+1}') 250 # shellcheck disable=SC2046 251 tail -n+$((firstrow+1)) "$file" | 252 awk '{ 253 if (NR==1) 254 { 255 printf "%s", $0; 256 times='$((maxcols-firstrowcols))'; 257 while (times-- > 0) 258 printf "\t"; 259 printf "\n"; 260 } 261 else print 262 }' | 263 head -n$((_rows-3)) | 264 ${tableprog} $(setopts) | 265 awk '{ 266 if (NR=='$((currow-firstrow))'+2) 267 print '\""$sgi"'7m'\"' $0 '\""$sgi"'0m'\"' 268 else print 269 }' 270 if [ "$donevipewarn" -eq 0 ] && [ ! "$editfilter" ]; then 271 error "warning: vipe not detected; input will be read \ 272 directly from stdin" 273 donevipewarn=1 274 fi 275 redraw=0 276 fi 277 278 if [ -z "$nextcmd" ]; then 279 if command -v rlwrap >/dev/null; then 280 #shellcheck disable=SC2016,SC2140 281 cmd=$(rlwrap -H "$historyfile" -s 1000 -D 2 -I -o \ 282 -S "${file##*/}[${_cols}x${_rows}+${firstrow}"\ 283 "@${currow}/$((maxrows-1))]> " sh -c 'IFS= read -r CMD && printf "%s\n" "$CMD"') 284 #shellcheck disable=SC2181 285 if [ "$?" -ne 0 ]; then 286 cmd="exit" 287 fi 288 else 289 printf "%s[%dx%d+%d@%d/%d]> " \ 290 "${file##*/}" "$_cols" "$_rows" "$firstrow" \ 291 "$currow" "$((maxrows-1))" 292 IFS= read -r cmd 293 fi 294 else 295 cmd="$nextcmd" 296 nextcmd='' 297 fi 298 299 IFS=' ' 300 #shellcheck disable=SC2086 301 set -- $cmd 302 cmd=$1 303 if [ -n "$2" ]; then 304 shift 305 args="$*" 306 else 307 args='' 308 fi 309 #shellcheck disable=SC2086 310 case "$cmd" in 311 /) set -- $args 312 searchcol='' 313 searchtext="$1" 314 if [ -n "$2" ]; then 315 searchcol="$1" 316 shift 317 searchtext="$*" 318 fi 319 if [ -z "$searchtext" ]; then 320 error "Argument required" 321 continue 322 fi 323 324 if [ -z "$wrapped" ]; then 325 if [ -z "$searchcol" ]; then 326 newrow=$(${findprog} -s $((currow + 1)) \ 327 "$file" "$searchtext") 328 else 329 newrow=$(${findprog} -s $((currow + 1)) \ 330 "$file" "$searchcol" "$searchtext") 331 fi 332 else 333 if [ -z "$searchcol" ]; then 334 newrow=$(${findprog} "$file" "$searchtext") 335 else 336 newrow=$(${findprog} "$file" \ 337 "$searchcol" "$searchtext") 338 fi 339 fi 340 wrapped='' 341 342 if [ -n "$newrow" ]; then 343 currow=$((newrow - 1)) 344 redraw=1 345 else 346 printf "Text '%s' not found" "$searchtext" 347 if [ -n "$searchcol" ]; then 348 printf " in column %d" "$searchcol" 349 fi 350 printf ". Search from top [Y/n]? " 351 IFS= read -r yesno 352 case "$yesno" in 353 n|N) ;; 354 *) currow=0 355 wrapped=1 356 if [ -z "$searchcol" ]; then 357 nextcmd="/ $searchtext" 358 else 359 nextcmd="/ $searchcol $searchtext" 360 fi;; 361 esac 362 fi 363 ;; 364 =) printf "Current row: %d\n" "$currow" 365 ;; 366 cols) case "$args" in 367 "") if [ "$cols" -eq 0 ]; then 368 # shellcheck disable=SC2140 369 printf "Current table width: %d (autodetected)"\ 370 "\n" "$_cols" 371 else 372 printf "Current table width: %d\n" "$cols" 373 fi;; 374 [0-9]*) cols="$args" 375 redraw=1;; 376 *) error "Not a number: '%s'" "$args";; 377 esac 378 ;; 379 delete|x) 380 ${delprog} "$file" "$((currow + 1))" 381 if [ "$currow" -eq "$((maxrows - 1))" ]; then 382 if [ "$maxrows" -ge 2 ]; then 383 currow=$((maxrows - 2)) 384 else 385 currow=0 386 fi 387 fi 388 if [ "$currow" -eq 0 ]; then 389 maxcols=$(sed 1q "$file" | tr -dc '\t' | wc -c | 390 awk '{print $1+1}') 391 fi 392 redraw=1 393 ;; 394 edit|e) if [ "$editfilter" ]; then 395 ${selprog} "$file" "$((currow + 1))" | ${editfilter} | 396 ${editprog} "$file" "$((currow + 1))" 397 else 398 error "warning: vipe not detected; input replaces row" 399 ${selprog} "$file" "$((currow + 1))" 400 cat | ${editprog} "$file" "$((currow + 1))" 401 fi 402 if [ "$currow" -eq 0 ]; then 403 maxcols=$(sed 1q "$file" | tr -dc '\t' | wc -c | 404 awk '{print $1+1}') 405 fi 406 redraw=1 407 ;; 408 exit|q) running=0 409 ;; 410 G) currow=$((maxrows - 1)) 411 redraw=1 412 ;; 413 g) case "$args" in 414 "") currow=0 415 redraw=1;; 416 [0-9]*) if [ "$args" -ge 0 ] && [ "$args" -lt "$maxrows" ]; then 417 currow="$args" 418 redraw=1 419 else 420 error "Row out of range: '%s'" "$args" 421 fi;; 422 *) error "Not a number: '%s'" "$args";; 423 esac 424 ;; 425 help|h|"") 426 help "$args" 427 ;; 428 insert|i) 429 case "$args" in 430 -) if [ "$editfilter" ]; then 431 ${selprog} "$file" "$((maxrows + 1))" | 432 ${editfilter} | 433 ${insprog} "$file" "$((currow))" 434 else 435 #error "warning: vipe not detected" 436 ${selprog} "$file" "$((maxrows + 1))" 437 cat | ${insprog} "$file" "$((currow))" 438 fi 439 if [ "$currow" -eq 0 ]; then 440 maxcols=$(sed 1q "$file" | tr -dc '\t' | 441 wc -c | awk '{print $1+1}') 442 fi 443 redraw=1;; 444 +) if [ "$editfilter" ]; then 445 ${selprog} "$file" "$((maxrows + 1))" | 446 ${editfilter} | 447 ${insprog} "$file" "$((currow + 1))" 448 else 449 #error "warning: vipe not detected" 450 ${selprog} "$file" "$((maxrows + 1))" 451 cat | ${insprog} "$file" "$((currow + 1))" 452 fi 453 currow=$((currow + 1)) 454 redraw=1;; 455 "") if [ "$editfilter" ]; then 456 ${selprog} "$file" "$((maxrows + 1))" | 457 ${editfilter} | 458 ${insprog} "$file" 459 else 460 #error "warning: vipe not detected" 461 ${selprog} "$file" "$((maxrows + 1))" 462 cat | ${insprog} "$file" 463 fi 464 currow=$((maxrows)) 465 redraw=1;; 466 *) error "Invalid argument: '%s'" "$args";; 467 esac 468 ;; 469 J) case "$args" in 470 ""|[0-9]*) 471 torow=$((currow + 1)) 472 if [ -n "$args" ]; then 473 torow=$((currow + args)) 474 fi 475 if [ "$torow" -lt "$maxrows" ]; then 476 ${moveprog} "$file" "$((currow+1))" "$((torow+1))" 477 currow="$torow" 478 redraw=1 479 else 480 error "Row out of range: '%s'" "$torow" 481 fi;; 482 *) error "Not a number: '%s'" "$args";; 483 esac 484 ;; 485 j) case "$args" in 486 ""|[0-9]*) 487 torow=$((currow + 1)) 488 if [ -n "$args" ]; then 489 torow=$((currow + args)) 490 fi 491 if [ "$torow" -lt "$maxrows" ]; then 492 currow="$torow" 493 redraw=1 494 else 495 error "Row out of range: '%s'" "$torow" 496 fi;; 497 *) error "Not a number: '%s'" "$args";; 498 esac 499 ;; 500 K) case "$args" in 501 ""|[0-9*]) 502 torow=$((currow - 1)) 503 if [ -n "$args" ]; then 504 torow=$((currow - args)) 505 fi 506 if [ "$torow" -ge 0 ]; then 507 ${moveprog} "$file" "$((currow+1))" "$((torow+1))" 508 currow="$torow" 509 redraw=1 510 else 511 error "Row out of range: '%s'" "$torow" 512 fi;; 513 *) error "Not a number: '%s'" "$args";; 514 esac 515 ;; 516 k) case "$args" in 517 ""|[0-9]*) 518 torow=$((currow - 1)) 519 if [ -n "$args" ]; then 520 torow=$((currow - args)) 521 fi 522 if [ "$torow" -ge 0 ]; then 523 currow="$torow" 524 redraw=1 525 else 526 error "Row out of range: '%s'" "$torow" 527 fi;; 528 *) error "Not a number: '%s'" "$args";; 529 esac 530 ;; 531 n) if [ -z "$searchtext" ]; then 532 error "No previous search" 533 else 534 if [ -z "$searchcol" ]; then 535 nextcmd="/ $searchtext" 536 else 537 nextcmd="/ $searchcol $searchtext" 538 fi 539 fi 540 ;; 541 print|p) 542 redraw=1 543 ;; 544 rows) case "$args" in 545 "") if [ "$rows" -eq 0 ]; then 546 # shellcheck disable=SC2140 547 printf "Current screen height: %d"\ 548 " (autodetected)\n" "$_rows" 549 else 550 printf "Current screen height: %d\n" "$rows" 551 fi;; 552 [0-9]*) rows="$args" 553 if [ "$rows" -ne 0 ] && [ "$rows" -lt 4 ]; then 554 rows=4 555 fi 556 redraw=1;; 557 *) error "Not a number: '%s'" "$args";; 558 esac 559 ;; 560 set) 561 case "$args" in 562 no*) value=0;; 563 *) value=1;; 564 esac 565 566 case "$args" in 567 *clear) clear=$value;; 568 *msdos) msdos=$value;; 569 "") printopt "clear" 570 printopt "msdos";; 571 *) error "Unknown parameter '%s'" "$args";; 572 esac 573 ;; 574 *) 575 error "Unknown command '%s'" "$cmd" 576 ;; 577 esac 578 done