You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
440 lines
11 KiB
Bash
440 lines
11 KiB
Bash
#!/usr/bin/env -S bash -e
|
|
#
|
|
# Copyright 2021 Nathan L. Conrad
|
|
#
|
|
# This program is free software: you can redistribute it and/or modify it under
|
|
# the terms of version 3 of the GNU General Public License as published by the
|
|
# Free Software Foundation.
|
|
#
|
|
# This program is distributed in the hope that it will be useful, but WITHOUT
|
|
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
|
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
|
# details.
|
|
|
|
# Establish usage
|
|
IFS= read -rd '' usage << '__eof__' || true
|
|
Usage: glitz [-h] [...] [-] [...]
|
|
|
|
Wrap shell invocation with styled status indications
|
|
|
|
Optional arguments:
|
|
-h, --help Show this help message and exit
|
|
-, -- Explicitly denote the end of optional arguments. Required if a
|
|
positional argument is supplied after any option begining with
|
|
'--'. Without insight into which long options expect parameters,
|
|
this differentiates the first positional argument from the
|
|
parameter of an option.
|
|
|
|
Arguments other than -h/--help are passed to the shell invocation, except for
|
|
the -i option, which is not supported. If the first positional parameter is
|
|
'---' and either the -c option is set or the -s option is not set, a command or
|
|
file is not executed. Instead, a sylized horizontal divider is written to
|
|
standard output.
|
|
|
|
Environment variables:
|
|
GLITZ_GLYPHS A string which specifies the character set used for
|
|
stylization. Valid values are '437' and 'nerd-font'.
|
|
Defaults to 7-bit ASCII if unset, null, or invalid.
|
|
GLITZ_SHELL The shell to invoke. Defaults to $SHELL if unset or null,
|
|
'/bin/sh' if SHELL is unset or null.
|
|
__eof__
|
|
|
|
# Usage: argerr msg
|
|
#
|
|
# Write an argument usage error message to standard error and fail
|
|
#
|
|
# Positional arguments:
|
|
# msg The core error message
|
|
function argerr
|
|
{
|
|
echo "$1. Use -h for help." >&2
|
|
exit 1
|
|
}
|
|
|
|
# Parse optional arguments
|
|
arg_index=0
|
|
cmd=0
|
|
stdin=0
|
|
long_opt=0
|
|
unset opts
|
|
while (( ++arg_index <= $# ))
|
|
do
|
|
case ${!arg_index} in
|
|
-|--)
|
|
(( ++arg_index ))
|
|
break
|
|
;;
|
|
---)
|
|
(( long_opt )) || break
|
|
;;
|
|
--help)
|
|
echo -n "$usage"
|
|
exit 0
|
|
;;
|
|
--*)
|
|
long_opt=1
|
|
;;
|
|
-*h*)
|
|
echo -n "$usage"
|
|
exit 0
|
|
;;
|
|
-*i*)
|
|
argerr '-i is not supported'
|
|
;;
|
|
-*c*)
|
|
cmd=1
|
|
stdin=0
|
|
;;
|
|
-*s*)
|
|
(( cmd )) || stdin=1
|
|
;;
|
|
-*)
|
|
;;
|
|
*)
|
|
(( long_opt )) || break
|
|
esac
|
|
opts+=("${!arg_index}")
|
|
done
|
|
|
|
# Usage: try [...]
|
|
#
|
|
# Execute arguments as a command while suppressing errors
|
|
function try
|
|
{
|
|
local errexit stdout err
|
|
[[ -o errexit ]] && errexit=1 || errexit=0
|
|
(( errexit )) && set +e
|
|
stdout=$("$@" 2> /dev/null)
|
|
err=$?
|
|
(( errexit )) && set -e
|
|
(( err )) || printf '%s\n' "$stdout"
|
|
}
|
|
|
|
# Cache the escape sequences of terminal attributes
|
|
color_names=(black red green yellow blue magenta cyan white grey)
|
|
normal=$(try tput sgr0)
|
|
unset bold colors bgnd fgnd
|
|
monochrome=1
|
|
for i in $(seq 0 $(( ${#color_names[@]} - 1 )))
|
|
do
|
|
eval "${color_names[$i]}=$i"
|
|
done
|
|
if [[ -n $normal ]]
|
|
then
|
|
bold=$(try tput bold)
|
|
colors=$(try tput colors)
|
|
monochrome=$(( colors < 256 ))
|
|
if (( ! monochrome ))
|
|
then
|
|
for i in $(seq 0 $(( ${#color_names[@]} - 1 )))
|
|
do
|
|
bgnd[$i]=$(try tput setab $i)
|
|
fgnd[$i]=$(try tput setaf $i)
|
|
done
|
|
fi
|
|
fi
|
|
|
|
# Cache glyphs
|
|
charmap=$(try locale charmap)
|
|
[[ $charmap = UTF-8 ]] && char_set=${GLITZ_GLYPHS:-ascii} || char_set=ascii
|
|
unset vbar ledge redge imargin omargin stime file tty xfer success failure \
|
|
timer positions
|
|
case $char_set in
|
|
437)
|
|
hbar=$'\xe2\x94\x80'
|
|
(( monochrome )) || vbar=$'\xe2\x94\x82'
|
|
overflow=$'\xc2\xbb'
|
|
(( monochrome )) && ledge=$bold[
|
|
(( monochrome )) && redge=$bold]
|
|
imargin=' '
|
|
(( monochrome )) && omargin=' '
|
|
for i in $(seq 0 9)
|
|
do
|
|
positions[$i]="$i "
|
|
done
|
|
;;
|
|
nerd-font)
|
|
hbar=$'\xe2\x94\x80'
|
|
overflow=$'\xe2\x80\xa6'
|
|
(( monochrome )) && ledge=$'\xee\x82\xb7' || ledge=$'\xee\x82\xb6'
|
|
(( monochrome )) && redge=$'\xee\x82\xb5' || redge=$'\xee\x82\xb4'
|
|
(( monochrome )) && imargin=' '
|
|
(( monochrome )) || omargin=' '
|
|
stime=$'\xef\x99\x94 '
|
|
file=$'\xef\x83\xb6 '
|
|
tty=$'\xef\x9a\x8c '
|
|
xfer=$'\xef\x92\x92 '
|
|
success=$'\xef\x89\x9b '
|
|
failure=$'\xee\x88\xb1 '
|
|
timer=$'\xef\x99\x90 '
|
|
positions[0]=$'\xef\xa2\xa1 '
|
|
positions[1]=$'\xef\xa2\xa4 '
|
|
positions[2]=$'\xef\xa2\xa7 '
|
|
positions[3]=$'\xef\xa2\xaa '
|
|
positions[4]=$'\xef\xa2\xad '
|
|
positions[5]=$'\xef\xa2\xb0 '
|
|
positions[6]=$'\xef\xa2\xb3 '
|
|
positions[7]=$'\xef\xa2\xb6 '
|
|
positions[8]=$'\xef\xa2\xb9 '
|
|
positions[9]=$'\xef\xa2\xbc '
|
|
;;
|
|
*)
|
|
hbar=-
|
|
(( monochrome )) || vbar='|'
|
|
overflow=$'...'
|
|
(( monochrome )) && ledge=$bold[
|
|
(( monochrome )) && redge=$bold]
|
|
imargin=' '
|
|
(( monochrome )) && omargin=' '
|
|
for i in $(seq 0 9)
|
|
do
|
|
positions[$i]="$i "
|
|
done
|
|
esac
|
|
|
|
# Usage: twidth
|
|
#
|
|
# Output the terminal width
|
|
function twidth
|
|
{
|
|
local cols
|
|
cols=$(try tput cols)
|
|
[[ -n $cols ]] || cols=80
|
|
echo "$cols"
|
|
}
|
|
|
|
# Usage: hdiv
|
|
#
|
|
# Output a horizontal divider and exit
|
|
function hdiv
|
|
{
|
|
echo -n "$normal${fgnd[$black]}"
|
|
printf "%.0s$hbar" $(seq $(twidth))
|
|
echo "$normal"
|
|
exit 0
|
|
}
|
|
|
|
# Usage: sanitize str
|
|
#
|
|
# Output a string with ugly whitespace and control codes removed
|
|
#
|
|
# Positional arguments:
|
|
# str A string
|
|
function sanitize
|
|
{
|
|
set -- "${1//$'\b'}"
|
|
set -- "${1//$'\f'}"
|
|
set -- "${1//$'\n'/' '}"
|
|
set -- "${1//$'\r'}"
|
|
set -- "${1//$'\t'/' '}"
|
|
set -- "${1//$'\v'/' '}"
|
|
printf '%s\n' "$1"
|
|
}
|
|
|
|
# Usage: dlen str
|
|
#
|
|
# Output the display length of a string
|
|
#
|
|
# Positional arguments:
|
|
# str A string
|
|
function dlen
|
|
{
|
|
set -- "${1//$normal}"
|
|
set -- "${1//$bold}"
|
|
for i in "${bgnd[@]}"
|
|
do
|
|
set -- "${1//$i}"
|
|
done
|
|
for i in "${fgnd[@]}"
|
|
do
|
|
set -- "${1//$i}"
|
|
done
|
|
printf '%s\n' "$1" | wc -L
|
|
}
|
|
|
|
# Usage: trunc str len
|
|
#
|
|
# Output a length-limited string
|
|
#
|
|
# Positional arguments:
|
|
# str A string
|
|
# len The maximum display length
|
|
function trunc
|
|
{
|
|
local len fmt
|
|
len=$(dlen "$1")
|
|
if (( len > $2 ))
|
|
then
|
|
local overflow_len
|
|
overflow_len=$(dlen "$overflow")
|
|
if (( $2 >= overflow_len ))
|
|
then
|
|
while true
|
|
do
|
|
set -- "${1%?}" "$2"
|
|
len=$(dlen "$1")
|
|
(( len > $2 - overflow_len )) || break
|
|
done
|
|
fmt=%s
|
|
set -- "$1$overflow"
|
|
fi
|
|
else
|
|
fmt=%s
|
|
fi
|
|
printf "$fmt\n" "$1"
|
|
}
|
|
|
|
# Usage: field str [bgnd] [fgnd]
|
|
#
|
|
# Output a stylized text field
|
|
#
|
|
# Positional arguments:
|
|
# str The field text
|
|
# bgnd The background color index
|
|
# fgnd The foreground color index
|
|
function field
|
|
{
|
|
local edge backgnd foregnd
|
|
backgnd=$2
|
|
if [[ -n $backgnd ]]
|
|
then
|
|
edge=$normal${fgnd[$backgnd]}
|
|
backgnd=${bgnd[$backgnd]}
|
|
fi
|
|
foregnd=$3
|
|
[[ -n $foregnd ]] && foregnd=${fgnd[$foregnd]}
|
|
printf %s "$edge$ledge$normal$backgnd$foregnd$imargin$1" \
|
|
"$normal$backgnd$foregnd$imargin$edge$redge$normal"
|
|
echo
|
|
}
|
|
|
|
# Default the shell
|
|
shell=${GLITZ_SHELL:-${SHELL:-/bin/sh}}
|
|
shell_name=$(basename -- "$shell")
|
|
|
|
# Use options and the first positional argument to infer the execution source.
|
|
# If the command string or filename argument is '---', output a horizontal
|
|
# divider and exit.
|
|
if (( cmd ))
|
|
then
|
|
(( arg_index > $# )) && argerr 'Missing command string argument'
|
|
arg=${!arg_index}
|
|
[[ $arg = --- ]] && hdiv
|
|
[[ -n $arg ]] || arg=:
|
|
(( ++arg_index ))
|
|
if [[ -n $SOCKHOP_SOCK ]]
|
|
then
|
|
(( arg_index <= $# )) && [[ ${!arg_index} = glitz ]] && \
|
|
set -- "${@:1:$(( arg_index - 1 ))}" "$shell_name" \
|
|
"${@:$(( arg_index + 1 ))}"
|
|
src=$(basename -- "$SOCKHOP_SOCK")
|
|
src=$xfer$(trunc "$(sanitize "$src")" 32)
|
|
else
|
|
src=$tty-c
|
|
fi
|
|
(( ++arg_index ))
|
|
printf -v src %s "$(field "$src" $white $black) " \
|
|
"${fgnd[$white]}$(sanitize "$arg")$normal"
|
|
elif (( stdin ))
|
|
then
|
|
src=$(field "${xfer}stdin" $white $black)
|
|
else
|
|
(( arg_index > $# )) && argerr 'Missing filename argument'
|
|
arg=${!arg_index}
|
|
[[ $arg = --- ]] && hdiv
|
|
(( ++arg_index ))
|
|
src=$(field "$file$(trunc "$(sanitize "$arg")" 32)" $white $black)
|
|
fi
|
|
|
|
# Stylize the start time and shell name
|
|
printf -v header %s "$(field "$stime$(date +%-I:%M%p)" $blue $black) " \
|
|
"${fgnd[$blue]}$(sanitize "$shell_name")$normal"
|
|
|
|
# While stylizing the shell options, ignore some redundancies
|
|
for i in "${opts[@]}"
|
|
do
|
|
case $i in
|
|
--*)
|
|
;;
|
|
-*c*|-*s*)
|
|
i=${i//c}
|
|
i=${i//s}
|
|
(( ${#i} < 2 )) && i=
|
|
esac
|
|
[[ -n $i ]] && header+=" ${fgnd[$blue]}$(sanitize "$i")$normal"
|
|
done
|
|
|
|
# Stylize the positional arguments
|
|
cols=$(twidth)
|
|
unset args
|
|
if (( $# - $arg_index < 9 ))
|
|
then
|
|
pos_args=("${@:$arg_index}")
|
|
line_len=0
|
|
[[ -n $ledge || -n $redge || -n $omargin ]] && arg_sep=$omargin || \
|
|
arg_sep=${bgnd[$cyan]}${fgnd[$black]}$vbar$normal
|
|
min_len=$(dlen "$(field "${positions[0]}")")
|
|
max_len=$(( cols - min_len ))
|
|
(( max_len < 32 )) && max_len=32
|
|
for i in $(seq 0 $(( ${#pos_args[@]} - 1 )))
|
|
do
|
|
(( line_len )) && sep=$arg_sep || sep=
|
|
sep_len=$(dlen "$sep")
|
|
arg=$(trunc "$(sanitize "${pos_args[$i]}")" $max_len)
|
|
arg=$(field "${positions[$(( i + 1 ))]}$arg" $cyan $black)
|
|
arg_len=$(dlen "$arg")
|
|
next_len=$(( line_len + sep_len + arg_len ))
|
|
if (( next_len > cols ))
|
|
then
|
|
printf -v args '%s\n%s' "$args" "$arg"
|
|
line_len=$arg_len
|
|
else
|
|
args+=$sep$arg
|
|
line_len=$next_len
|
|
fi
|
|
done
|
|
fi
|
|
|
|
# Join and output the header
|
|
line_len=$(dlen "$header")
|
|
src_len=$(dlen "$src")
|
|
if [[ -n $args ]]
|
|
then
|
|
(( cmd )) && sep=' ' || sep=$omargin
|
|
sep_len=$(dlen "$sep")
|
|
arg_len=$(dlen "$args")
|
|
if (( src_len + sep_len + arg_len > cols ))
|
|
then
|
|
(( line_len + src_len + 1 > cols )) && \
|
|
printf '%s\n' "$header" "$src" || echo "$header $src"
|
|
echo "$args"
|
|
else
|
|
(( line_len + src_len + sep_len + arg_len + 1 > cols )) && \
|
|
printf '%s\n' "$header" "$src$sep$args" ||
|
|
echo "$header $src$sep$args"
|
|
fi
|
|
else
|
|
(( line_len + src_len + 1 > cols )) && printf '%s\n' "$header" "$src" || \
|
|
echo "$header $src"
|
|
fi
|
|
|
|
# Invoke the shell
|
|
exit_status=0
|
|
start_nsecs=$(date +%s%N)
|
|
"$shell" "$@" || exit_status=$?
|
|
nsecs=$(date +%s%N)
|
|
(( nsecs > start_nsecs )) && (( nsecs -= start_nsecs )) || nsecs=0
|
|
|
|
# Output the exit status and execution time
|
|
(( exit_status )) && \
|
|
exit_status=$(field "$failure$exit_status" $red $black) || \
|
|
exit_status=$(field "${success}OK" $green $black)
|
|
if (( nsecs < 59950000000 ))
|
|
then
|
|
dsecs=$(( (nsecs + 50000000) / 100000000 ))
|
|
secs=$(( dsecs / 10 )).$(( dsecs % 10 ))
|
|
else
|
|
secs=$(( (nsecs + 500000000) / 1000000000 ))
|
|
fi
|
|
echo "$exit_status$omargin$(field "$timer${secs}s" $blue $black)"
|