My Nix configuration. Enter at your own risk.
0

Configure Feed

Select the types of activity you want to include in your feed.

dotnix / scripts / run-quiet
5.4 kB 179 lines
1#!/usr/bin/env bash 2# Usage: run-quiet TITLE -- COMMAND [ARGS...] 3# 4# Runs COMMAND while showing a spinner and the last PREVIEW_LINES of output 5# beneath it. On success, the live area is wiped clean. On failure, the 6# full captured output is printed and the command's exit code is returned. 7# 8# The command is run inside a pseudo-TTY (via util-linux `script`) so that 9# tools which disable color when their stdout isn't a terminal still produce 10# colored output. 11# 12# Configure preview height with the PREVIEW_LINES env var (default: 5). 13 14set -euo pipefail 15 16PREVIEW_LINES=${PREVIEW_LINES:-5} 17 18if [[ $# -lt 3 ]]; then 19 echo "Usage: $0 TITLE -- COMMAND [ARGS...]" >&2 20 exit 2 21fi 22 23title="$1" 24shift 25if [[ "${1:-}" != "--" ]]; then 26 echo "Expected '--' after TITLE" >&2 27 exit 2 28fi 29shift 30 31log=$(mktemp) 32pid="" 33cleanup() { 34 if [[ -n "$pid" ]]; then 35 kill "$pid" 2>/dev/null || true 36 wait "$pid" 2>/dev/null || true 37 fi 38 tput cnorm 2>/dev/null || true 39 rm -f "$log" 40} 41trap cleanup EXIT INT TERM 42 43# Hide the cursor while spinning 44tput civis 2>/dev/null || true 45 46frames=('⠋' '⠙' '⠹' '⠸' '⠼' '⠴' '⠦' '⠧' '⠇' '⠏') 47n_frames=${#frames[@]} 48total=$((PREVIEW_LINES + 1)) 49back=$((total - 1)) 50 51# Styles 52RESET=$'\033[0m' 53BOLD=$'\033[1m' 54DIM=$'\033[2m' 55MAGENTA=$'\033[35m' 56 57PREFIX="${DIM}${RESET} " 58PREFIX_WIDTH=2 59 60# Reserve vertical space and move to the top of the live area 61for ((i = 0; i < back; i++)); do echo; done 62printf '\033[%dA\r' "$back" 63 64# Pull leading sudo (+ its short flags) out of the script -c payload so 65# sudo runs in the user's real tty (cache hit) rather than inside the 66# new PTY (cache miss with no stdin to read a password from). 67sudo_prefix=() 68if [[ "${1:-}" == "sudo" ]]; then 69 sudo_prefix+=("$1"); shift 70 while [[ ${#@} -gt 0 && "${1}" == -* ]]; do 71 sudo_prefix+=("$1"); shift 72 done 73fi 74 75# Run inside a PTY so the command produces colored output. BSD `script` 76# (macOS) and util-linux `script` (Linux) take incompatible flags, so 77# branch on OS: 78# -q: quiet (both) 79# -e: return the child's exit status (both) 80# -F (BSD) / -f (util-linux): flush after each write 81# BSD takes the command as positional args after the typescript file; 82# util-linux needs the command as a single string via -c. 83if [[ "$(uname)" == "Darwin" ]]; then 84 ${sudo_prefix[@]+"${sudo_prefix[@]}"} script -qeF /dev/null "$@" > "$log" 2>&1 & 85else 86 cmd_str="" 87 for arg in "$@"; do 88 cmd_str+="$(printf '%q ' "$arg")" 89 done 90 ${sudo_prefix[@]+"${sudo_prefix[@]}"} script -qefc "$cmd_str" /dev/null > "$log" 2>&1 & 91fi 92pid=$! 93 94frame=0 95while kill -0 "$pid" 2>/dev/null; do 96 cols=$(tput cols) 97 content_cols=$((cols - PREFIX_WIDTH)) 98 title_cols=$((cols - 2)) 99 [ "$content_cols" -lt 1 ] && content_cols=1 100 [ "$title_cols" -lt 1 ] && title_cols=1 101 102 # --- Title line --- 103 spin="${frames[$((frame % n_frames))]}" 104 raw_title="${title:0:title_cols}" 105 title_padded=$(printf '%-*s' "$title_cols" "$raw_title") 106 printf '%s%s%s %s%s%s\n' \ 107 "$MAGENTA" "$spin" "$RESET" \ 108 "$BOLD" "$title_padded" "$RESET" 109 110 # --- Preview lines --- 111 # Normalize line endings: strip trailing \r (from CRLF), then convert 112 # any standalone \r (progress-bar updates) to \n. 113 sed 's/\r$//' "$log" | tr '\r' '\n' \ 114 | awk -v max="$content_cols" \ 115 -v prefix="$PREFIX" \ 116 -v dim="$DIM" \ 117 -v reset="$RESET" \ 118 -v n="$PREVIEW_LINES" ' 119 function fit(s, m, out, vis, i, c, len, j, esc) { 120 out = ""; vis = 0; esc = "" 121 len = length(s) 122 for (i = 1; i <= len; i++) { 123 c = substr(s, i, 1) 124 if (esc != "") { 125 esc = esc c 126 if (c ~ /[A-Za-z]/) { 127 if (c == "m") out = out esc dim 128 esc = "" 129 } 130 } else if (c == "\033") { 131 esc = c 132 } else { 133 if (vis >= m) break 134 out = out c; vis++ 135 } 136 } 137 for (j = vis; j < m; j++) out = out " " 138 return out 139 } 140 # Skip lines that are empty/whitespace-only after stripping ANSI. 141 { 142 v = $0 143 gsub(/\033\[[0-9;]*[a-zA-Z]/, "", v) 144 if (v ~ /^[[:space:]]*$/) next 145 lines[count++] = $0 146 } 147 END { 148 empty = "" 149 for (j = 0; j < max; j++) empty = empty " " 150 start = count > n ? count - n : 0 151 shown = count - start 152 for (i = 0; i < n; i++) { 153 nl = (i < n - 1 ? "\n" : "") 154 if (i < shown) { 155 printf "%s%s%s%s%s", prefix, dim, fit(lines[start + i], max), reset, nl 156 } else { 157 printf "%s%s%s", prefix, empty, nl 158 } 159 } 160 } 161 ' 162 163 printf '\033[%dA\r' "$back" 164 165 frame=$((frame + 1)) 166 sleep 0.1 167done 168 169ret=0 170wait "$pid" || ret=$? 171pid="" 172 173printf '\033[J' 174 175if [ "$ret" -ne 0 ]; then 176 gum log --level error "$title (exit $ret)" 177 sed 's/\r$//' "$log" 178 exit "$ret" 179fi