Skip to content

Commit

Permalink
Use column to layout footer
Browse files Browse the repository at this point in the history
Realistically, we only need to print a footer in two modes:

1) using the explicit column layout designated by the caller - typically
   three columns with equal padding in columns

2) using a single column, due to a narrow screen

`column` supports both of these use cases by padding columnar data (as
indicated by embedded line returns in stdin).

item1:item2:item3
item4::item5

Would result in two rows with three columns. The second row would have
an empty middle column. By simply replacing ':' with a line return, we
can force everything into a single column.

`column` is not provided by BusyBox, but is part of util-linux. If the
binary is not found on the host system at build time it is internally
disabled via the `HAS_COLUMN` use flag. A subset of text is used on each
screen in that condition, or if the width is less than 80 characters.

Closes #254
Closes #256
  • Loading branch information
zdykstra committed Feb 11, 2022
1 parent 489fa37 commit ec89a7c
Show file tree
Hide file tree
Showing 2 changed files with 59 additions and 103 deletions.
8 changes: 7 additions & 1 deletion zfsbootmenu/install-helpers.sh
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ zfsbootmenu_essential_binaries=(
"tac"
"blkid"
"awk"
"fold"
"ps"
"env"
"chmod"
Expand All @@ -42,6 +41,7 @@ zfsbootmenu_essential_binaries=(
# shellcheck disable=SC2034
zfsbootmenu_optional_binaries=(
"mbuffer"
"column"
)

# shellcheck disable=SC2034
Expand Down Expand Up @@ -90,6 +90,11 @@ create_zbm_conf() {
has_refresh=1
fi

local has_column
if command -v column >/dev/null 2>&1 ; then
has_column=1
fi

cat > "${BUILDROOT}/etc/zfsbootmenu.conf" <<-'EOF'
# Include guard
[ -n "${_ETC_ZFSBOOTMENU_CONF}" ] && return
Expand All @@ -99,6 +104,7 @@ create_zbm_conf() {
cat >> "${BUILDROOT}/etc/zfsbootmenu.conf" <<-EOF
export BYTE_ORDER="${endian:-le}"
export HAS_REFRESH="${has_refresh}"
export HAS_COLUMN="${has_column}"
EOF
}

Expand Down
154 changes: 52 additions & 102 deletions zfsbootmenu/lib/zfsbootmenu-lib.sh
Original file line number Diff line number Diff line change
Expand Up @@ -32,99 +32,30 @@ csv_cat() {
(IFS=',' ; printf '%s' "${CSV[*]}")
}

# arg1...argN: tokens to wrap
# prints: string, wrapped to width without breaking tokens
# arg1: colon-delimited string
# arg2: short string used when column is missing / display is small
# prints: string, columnized
# returns: nothing

header_wrap() {
local hardbreak tokens tok footer nlines allow_breaks maxcols curcols

# Pick a wrap width if none was specified
if [ -z "${wrap_width}" ]; then
wrap_width="$(( $( tput cols 2>/dev/null ) - 4 ))"
[ "${wrap_width}" -gt 0 ] || wrap_width=80
fi

# Pick a uniform field width
if [ -z "${field_width}" ]; then
field_width=0
for tok in "$@"; do
[ "${#tok}" -gt "${field_width}" ] && field_width="${#tok}"
done
fi

# Determine maximum number of columns
maxcols=0
curcols=0
for tok in "$@"; do
if [ -z "${tok}" ]; then
if [ "${curcols}" -gt "${maxcols}" ]; then
maxcols="${curcols}"
fi
curcols=0
else
(( curcols=curcols+1 ))
fi
done

# Honor hard breaks only if terminal is sufficiently tall and wide
nlines="$( tput lines 2>/dev/null )" || nlines=0
if [ "${nlines}" -ge 24 ] && [ "${wrap_width}" -ge "$(( maxcols * field_width ))" ]; then
allow_breaks=1
else
allow_breaks=0
column_wrap() {
local footer
footer="${1}"

if [ "${COLUMNS}" -lt 80 ] || [ -z "${HAS_COLUMN}" ] ; then
# Use shorter footer text, if it exists
[ -n "${2}" ] && footer="${2}"
shopt -s extglob
# Collapse repeated colons into a single
footer="${footer//+([:])/:}"
footer="${footer//:/$'\n'}"
shopt -u extglob
else
footer="$( echo -e "${footer}" | column -t -s ':' )"
fi

# Processing is done line-by-line
while [ $# -gt 0 ]; do
hardbreak=0
tokens=()

# Process up to the first empty string, which is a hard break
while [ $# -gt 0 ]; do
# Pad fields if a uniform field width was assigned
if [ "${field_width}" -gt 0 ] && [ -n "$1" ]; then
tok="$( printf "%-${field_width}s" "$1" )"
tok="${tok// /_}"
else
tok="${1// /_}"
fi

shift

if [ -n "${tok}" ]; then
# If breaks are disallowed, also suppress all-empty tokens
[ "${allow_breaks}" -eq 1 ] || [ -n "${tok//_/}" ] || continue;
tokens+=( "${tok}" )
elif [ "${allow_breaks}" -eq 1 ]; then
# Hard wrap on empty tokens only with sufficient space
hardbreak=1
break
fi
done

# Print the header if there were tokens
if [ "${#tokens[@]}" -gt 0 ]; then
# Only try to wrap if the width is long enough
if [ "${wrap_width}" -gt 0 ]; then
[ "$(( field_width + 4 ))" -ge "${wrap_width}" ] && footer="${footer//_/ }"
footer="$( echo -n -e "${tokens[@]}" | fold -s -w "${wrap_width}" )"
else
footer="$( echo -n -e "${tokens[@]}" )"
fi

# Add some color for emphasis
footer="${footer//\[/\\033\[0;32m\[}"
footer="${footer//\]/\]\\033\[0m}"

echo -n -e "${footer//_/ }"
fi

# Add a hard break if an empty token was provided
if [ "${hardbreak}" -ne 0 ]; then
echo ""
fi
done
footer="${footer//\[/\\033\[0;32m\[}"
footer="${footer//\]/\]\\033\[0m}"
echo -e "${footer}"
}

# arg1: Path to file with detected boot environments, 1 per line
Expand All @@ -147,11 +78,15 @@ draw_be() {

zdebug "using environment file: ${env}"

header="$( header_wrap \
"[RETURN] boot" "[ESCAPE] refresh view" "[CTRL+P] pool status" "" \
"[CTRL+D] set bootfs" "[CTRL+S] snapshots" "[CTRL+K] kernels" "" \
"[CTRL+E] edit kcl" "[CTRL+J] jump into chroot" "[CTRL+R] recovery shell" "" \
"[CTRL+L] view logs" " " "[CTRL+H] help" )"
header="$( column_wrap "\
[RETURN] boot:[ESCAPE] refresh view:[CTRL+P] pool status
[CTRL+D] set bootfs:[CTRL+S] snapshots:[CTRL+K] kernels
[CTRL+E] edit kcl:[CTRL+J] jump into chroot:[CTRL+R] recovery shell
[CTRL+L] view logs::[CTRL+H] help" \
"\
[RETURN] boot
[CTRL+R] recovery shell
[CTRL+H] help" )"

expects="--expect=alt-e,alt-k,alt-d,alt-s,alt-c,alt-r,alt-p,alt-w,alt-j,alt-o"

Expand Down Expand Up @@ -191,9 +126,14 @@ draw_kernel() {

zdebug "using kernels file: ${_kernels}"

header="$( header_wrap "[RETURN] boot" "[ESCAPE] back" "" \
"[CTRL+D] set default" "[CTRL+U] unset default" "" \
"[CTRL+L] view logs" "[CTRL+H] help" )"
header="$( column_wrap "\
[RETURN] boot:[ESCAPE] back
[CTRL+D] set default:[CTRL+U] unset default
[CTRL+L] view logs:[CTRL+H] help" \
"\
[RETURN] boot
[CTRL+D] set default
[CTRL+H] help" )"

expects="--expect=alt-d,alt-u"

Expand Down Expand Up @@ -229,9 +169,16 @@ draw_snapshots() {

sort_key="$( get_sort_key )"

header="$( header_wrap "[RETURN] duplicate" "[CTRL+C] clone only" "[CTRL+X] clone and promote" "" \
"[CTRL+N] create new snapshot" "[CTRL+J] jump into chroot" "[CTRL+D] show diff" "" \
"[CTRL+L] view logs" " " "[CTRL+H] help" "" "[ESCAPE] back" " " "[CTRL+R] rollback" )"
header="$( column_wrap "\
[RETURN] duplicate:[CTRL+C] clone only:[CTRL+X] clone and promote
[CTRL+D] show diff:[CTRL+R] rollback:[CTRL+N] create new snapshot
[CTRL+L] view logs::[CTRL+J] jump into chroot
[CTRL+H] help::[ESCAPE] back" \
"\
[RETURN] duplicate
[CTRL+D] show diff
[CTRL+H] help" )"

context="Note: for diff viewer, use tab to select/deselect up to two items"

expects="--expect=alt-x,alt-c,alt-j,alt-o,alt-n,alt-r"
Expand Down Expand Up @@ -349,8 +296,11 @@ draw_pool_status() {
[ "${psize}" -le 0 ] && psize=10

# Override uniform field width to force once item per line
header="$( field_width=0 header_wrap "[ESCAPE] back" "" \
"[CTRL+R] rewind checkpoint" "" "[CTRL+L] view logs" "" "[CTRL+H] help" )"
header="$( column_wrap "\
[ESCAPE] back
[CTRL+R] rewind checkpoint
[CTRL+L] view logs
[CTRL+H] help" )"

if ! selected="$( zpool list -H -o name |
HELP_SECTION=zpool-health ${FUZZYSEL} \
Expand Down

0 comments on commit ec89a7c

Please sign in to comment.