ste

Једноставни уређивач табела
git clone https://git.sr.ht/~strahinja/ste
Дневник | Датотеке | Референце | ПРОЧИТАЈМЕ | ЛИЦЕНЦА

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