diff options
author | Bryan Drewery <bryan@shatow.net> | 2022-09-14 23:03:31 +0300 |
---|---|---|
committer | Bryan Drewery <bryan@shatow.net> | 2022-11-05 18:47:57 +0300 |
commit | ffefb5706663204f56271bad77621022abfdd9cf (patch) | |
tree | 832f4105c278742c78eeccd541cc5e2f42bf258e /src/share | |
parent | 4a74af201588e35faf734b51c78ca2c8e9a92b46 (diff) |
display_output: Allow filtering and re-ordering columns
This brings some major refactoring.
Diffstat (limited to 'src/share')
-rw-r--r-- | src/share/poudriere/include/display.sh | 249 |
1 files changed, 184 insertions, 65 deletions
diff --git a/src/share/poudriere/include/display.sh b/src/share/poudriere/include/display.sh index ca9c218f..ff17c5f3 100644 --- a/src/share/poudriere/include/display.sh +++ b/src/share/poudriere/include/display.sh @@ -24,56 +24,120 @@ # OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF # SUCH DAMAGE. +EX_DATAERR=65 +EX_SOFTWARE=70 + +DISPLAY_SEP=$'\002' +DISPLAY_DYNAMIC_FORMAT_DEFAULT="%%-%ds" +DISPLAY_TRIM_TRAILING_FIELD=1 + display_setup() { [ $# -eq 1 ] || [ $# -eq 2 ] || eargs display_setup format [column_sort] + local IFS + local -; set -f + _DISPLAY_DATA= - _DISPLAY_FORMAT="$1" + _DISPLAY_FORMAT="${1:-dynamic}" + _DISPLAY_HEADER= _DISPLAY_COLUMN_SORT="${2-}" _DISPLAY_FOOTER= + _DISPLAY_LINES=0 + _DISPLAY_COLS=0 + + # encode + set -- ${_DISPLAY_FORMAT} + IFS="${DISPLAY_SEP}" + _DISPLAY_FORMAT="$@" + unset IFS } display_add() { - local arg line tab + [ $# -gt 0 ] || eargs display_add col [col...] + local IFS - unset line - tab=$'\t' - # Ensure blank arguments and spaced-arguments are respected. - # This is mostly to deal with sorting later - for arg do - if [ -z "${arg}" ]; then - arg=" " + if [ -z "${_DISPLAY_HEADER}" ]; then + local arg argi line argformat format + + argi=1 + unset line + format= + for arg do + # Collect header custom formats if using dynamic + if [ "${_DISPLAY_FORMAT}" == "dynamic" ]; then + case "${arg}" in + *:*%%*) + argformat="${arg#*:}" + arg="${arg%%:*}" + ;; + *) + argformat="${DISPLAY_DYNAMIC_FORMAT_DEFAULT}" + ;; + esac + format="${format:+${format}${DISPLAY_SEP}}${argformat}" + fi + line="${line:+${line}${DISPLAY_SEP}}${arg}" + hash_set _display_header "${arg}" "${argi}" + argi=$((argi + 1)) + done + _DISPLAY_COLS=$((argi - 1)) + _DISPLAY_HEADER="${line}" + if [ "${_DISPLAY_FORMAT}" == "dynamic" ]; then + _DISPLAY_FORMAT="${format}" fi - line="${line:+${line}${tab}}${arg}" - done + + return + fi + # Add in newline - if [ -n "${_DISPLAY_DATA}" ]; then + if [ -n "${_DISPLAY_DATA-}" ]; then _DISPLAY_DATA="${_DISPLAY_DATA}"$'\n' fi - _DISPLAY_DATA="${_DISPLAY_DATA:+${_DISPLAY_DATA}}${line}" - return 0 + _DISPLAY_LINES=$((_DISPLAY_LINES + 1)) + # encode + IFS="${DISPLAY_SEP}" + _DISPLAY_DATA="${_DISPLAY_DATA:+${_DISPLAY_DATA}}""$@" + unset IFS } display_footer() { - local arg line tab + local IFS + + # encode + IFS="${DISPLAY_SEP}" + _DISPLAY_FOOTER="$@" + unset IFS +} - unset line - tab=$'\t' - # Ensure blank arguments and spaced-arguments are respected. - for arg do +_display_check_lengths() { + local cnt arg max_length + local IFS + local -; set -f + + # decode + IFS="${DISPLAY_SEP}" + set -- $@ + unset IFS + + cnt=0 + for arg in "$@"; do + cnt=$((cnt + 1)) if [ -z "${arg}" ]; then - arg=" " + continue + fi + stripansi "${arg}" arg + hash_get _display_lengths "${cnt}" max_length || max_length=0 + if [ "${#arg}" -gt "${max_length}" ]; then + hash_set _display_lengths "${cnt}" "${#arg}" fi - line="${line:+${line}${tab}}${arg}" done - # Add in newline - _DISPLAY_FOOTER="${line}" - return 0 } +# display_output [col ...] display_output() { - local cnt lengths length format arg flag quiet line n - local header header_format + local lengths format arg flag quiet line n + local cols header_format local OPTIND=1 + local IFS local - set -f @@ -93,45 +157,84 @@ display_output() { shift $((OPTIND-1)) - format="${_DISPLAY_FORMAT}" + # cols to filter/reorder on + cols= + if [ "$#" -gt 0 ]; then + local col awktmp - # Determine optimal format - n=0 - while IFS= mapfile_read_loop_redir line; do - n=$((n + 1)) - if [ "${n}" -eq 1 ]; then - if [ "${quiet}" -eq 1 ]; then - continue - fi - header="${line}" - fi - IFS=$'\t' - set -- ${line} - unset IFS - cnt=0 + _DISPLAY_COLS=0 for arg in "$@"; do - hash_get lengths ${cnt} max_length || max_length=0 - stripansi "${arg}" arg - if [ ${#arg} -gt ${max_length} ]; then - # Keep the hash var local to this function - _hash_var_name "lengths" "${cnt}" - local ${_hash_var_name} - # Set actual value - hash_set lengths ${cnt} ${#arg} + if ! hash_remove _display_header "${arg}" col; then + err ${EX_DATAERR:?} "No column named '${arg}'" fi - cnt=$((cnt + 1)) + # cols="$3,$2,$1" for awk printing + cols="${cols:+${cols},}\$${col}" + _DISPLAY_COLS=$((_DISPLAY_COLS + 1)) done + + # Re-order and filter using awk(1) back into our internal vars. + awktmp=$(mktemp -t display_output) + { + echo "${_DISPLAY_FORMAT}" + echo "${_DISPLAY_HEADER}" + echo "${_DISPLAY_DATA}" | + sort -t "${DISPLAY_SEP}" ${_DISPLAY_COLUMN_SORT} + echo "${_DISPLAY_FOOTER}" + } > "${awktmp}.in" + awk -F"${DISPLAY_SEP}" -vOFS="${DISPLAY_SEP}" \ + "{print ${cols}}" "${awktmp}.in" > "${awktmp}" + n=-1 + while IFS= mapfile_read_loop "${awktmp}" line; do + case "${n}" in + -1) + unset _DISPLAY_DATA + _DISPLAY_FORMAT="${line}" + ;; + 0) + _DISPLAY_HEADER="${line}" + ;; + "$((_DISPLAY_LINES + 1))") + if [ -n "${_DISPLAY_FOOTER}" ]; then + _DISPLAY_FOOTER="${line}" + fi + ;; + *) + if [ -n "${_DISPLAY_DATA-}" ]; then + _DISPLAY_DATA="${_DISPLAY_DATA}"$'\n' + fi + _DISPLAY_DATA="${_DISPLAY_DATA:+${_DISPLAY_DATA}}${line}" + ;; + esac + n=$((n + 1)) + done + rm -f "${awktmp}" "${awktmp}.in" + else + _DISPLAY_DATA="$(echo "${_DISPLAY_DATA}" | + sort -t "${DISPLAY_SEP}" ${_DISPLAY_COLUMN_SORT})" + fi + + # Determine optimal format from filtered data + _display_check_lengths "${_DISPLAY_HEADER}" + _display_check_lengths "${_DISPLAY_FOOTER}" + while IFS= mapfile_read_loop_redir line; do + _display_check_lengths "${line}" done <<-EOF ${_DISPLAY_DATA} - ${_DISPLAY_FOOTER} EOF # Set format lengths if format is dynamic width + # decode + IFS="${DISPLAY_SEP}" + set -- ${_DISPLAY_FORMAT} + unset IFS + format="$@" case "${format}" in *%%*) + local length + set -- ${format} lengths= - n=0 + n=1 for arg in "$@"; do # Check if this is a format argument case "${arg}" in @@ -140,9 +243,18 @@ display_output() { esac case ${arg} in *%d*) - hash_get lengths ${n} length + hash_remove _display_lengths "${n}" length + if [ "${DISPLAY_TRIM_TRAILING_FIELD}" -eq 1 ] && + [ "${n}" -eq "${_DISPLAY_COLS}" ]; then + case "${arg}" in + *-*) length=0 ;; + esac + fi lengths="${lengths:+${lengths} }${length}" ;; + *) + hash_unset _display_lengths "${n}" || : + ;; esac n=$((n + 1)) done @@ -150,32 +262,39 @@ display_output() { ;; esac - # Show header separately so it is not sorted + # Header if [ "${quiet}" -eq 0 ]; then - stripansi "${header}" header + stripansi "${_DISPLAY_HEADER}" _DISPLAY_HEADER stripansi "${format}" header_format - IFS=$'\t' - set -- ${header} + # decode + IFS="${DISPLAY_SEP}" + set -- ${_DISPLAY_HEADER} unset IFS printf "${header_format}\n" "$@" fi - # Sort as configured in display_setup() - echo "${_DISPLAY_DATA}" | tail -n +2 | \ - sort -t $'\t' ${_DISPLAY_COLUMN_SORT} | \ - while IFS= mapfile_read_loop_redir line; do - IFS=$'\t' + # Data + while IFS= mapfile_read_loop_redir line; do + # decode + IFS="${DISPLAY_SEP}" set -- ${line} unset IFS printf "${format}\n" "$@" - done + done <<-EOF + ${_DISPLAY_DATA} + EOF + + # Footer if [ -n "${_DISPLAY_FOOTER}" ]; then - IFS=$'\t' + # decode + IFS="${DISPLAY_SEP}" set -- ${_DISPLAY_FOOTER} unset IFS printf "${format}\n" "$@" fi unset _DISPLAY_DATA _DISPLAY_FORMAT \ - _DISPLAY_COLUMN_SORT _DISPLAY_FOOTER + _DISPLAY_COLUMN_SORT \ + _DISPLAY_LINES _DISPLAY_COLS \ + _DISPLAY_FOOTER _DISPLAY_HEADER } |