blob: fccff904b0773aa3594da6058d2225432f1ca23c [file]
################################################################################
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
################################################################################
# Presentation layer for IT run tooling (sourced, not executed directly).
UI_USE_COLOR=0
UI_SPINNER_PID=""
ui_init() {
UI_USE_COLOR=0
if [ -t 1 ] && [ -z "${NO_COLOR:-}" ] && [ -z "${CI:-}" ]; then
UI_USE_COLOR=1
fi
}
ui__color_enabled() {
[ "$UI_USE_COLOR" -eq 1 ]
}
ui__fg() {
local code="$1"
ui__color_enabled && printf '\033[%sm' "$code"
}
ui__reset() { ui__fg 0; }
ui__bold() { ui__fg 1; }
ui__dim() { ui__fg 2; }
ui__red() { ui__fg 31; }
ui__grn() { ui__fg 32; }
ui__ylw() { ui__fg 33; }
ui__blu() { ui__fg 34; }
ui__mag() { ui__fg 35; }
ui__cyn() { ui__fg 36; }
ui__line() {
local glyph="$1"
local color_fn="$2"
local indent="$3"
local message="$4"
local stream="${5:-1}"
local plain_label="${6:-}"
if ui__color_enabled; then
{ $color_fn; printf '%s%s ' "$indent" "$glyph"; ui__reset; printf '%s\n' "$message"; } >&"$stream"
elif [ "$stream" -eq 2 ]; then
printf '%s: %s\n' "$plain_label" "$message" >&2
else
printf '%s%s\n' "$indent" "$message"
fi
}
ui__glyph_status() {
local ok="$1"
local message="$2"
local stream="${3:-2}"
if ui__color_enabled; then
if [ "$ok" -eq 0 ]; then
{ ui__grn; printf ' ✔ '; ui__reset; echo "$message"; } >&"$stream"
else
{ ui__red; printf ' ✖ '; ui__reset; echo "$message (failed)"; } >&"$stream"
fi
elif [ "$ok" -ne 0 ]; then
echo "ERROR: $message" >&"$stream"
fi
}
ui__banner_box() {
local title="$1"
if ui__color_enabled; then
ui__cyn; ui__bold
cat <<'EOF'
╔═══════════════════════════════════════════════════════════╗
██╗ ██╗███╗ ██╗ ██████╗ ███╗ ███╗██╗
██║ ██║████╗ ██║██╔═══██╗████╗ ████║██║
██║ ██║██╔██╗ ██║██║ ██║██╔████╔██║██║
██║ ██║██║╚██╗██║██║ ██║██║╚██╔╝██║██║
╚██████╔╝██║ ╚████║╚██████╔╝██║ ╚═╝ ██║██║
╚═════╝ ╚═╝ ╚═══╝ ╚═════╝ ╚═╝ ╚═╝╚═╝
EOF
printf ' ║ %-57s║\n' "$title"
echo ' ╚═══════════════════════════════════════════════════════════╝'
ui__reset
else
echo "$title"
fi
}
ui__banner_with_tagline() {
local title="$1"
local tagline="$2"
ui__banner_box "$title"
if [ -z "$tagline" ]; then
return
fi
if ui__color_enabled; then
ui__dim; printf ' '; ui__reset
ui_typewriter "$tagline"
echo
else
echo "$tagline"
fi
}
ui_banner() {
ui__banner_with_tagline "IT RUN ARCHIVER · pack · triage · ship" \
'Collecting artifacts for humans and LLMs alike...'
}
ui_banner_compare() {
ui__banner_with_tagline "IT RUN COMPARATOR · systematic · flaky · drift" \
'Diffing captures to separate real bugs from noise...'
}
ui_typewriter() {
local text="$1"
local delay="${2:-0.012}"
local i
if ! ui__color_enabled; then
echo "$text"
return
fi
for ((i = 0; i < ${#text}; i++)); do
printf '%s' "${text:i:1}"
sleep "$delay"
done
echo
}
ui_phase() {
local step="$1"
local label="$2"
if ui__color_enabled; then
ui__blu; ui__bold
printf '▸ [%s] %s\n' "$step" "$label"
ui__reset
else
printf '[%s] %s\n' "$step" "$label"
fi
}
ui_detail() { ui__line '→' ui__dim ' ' "$1"; }
ui_warn() { ui__line '⚠' ui__ylw ' ' "$1" 2 WARN; }
ui_error() { ui__line '✖' ui__red ' ' "$1" 2 ERROR; }
ui_metric() {
local label="$1"
local value="$2"
if ui__color_enabled; then
ui__dim; printf ' %-14s' "$label"; ui__reset
ui__bold; printf '%s\n' "$value"; ui__reset
else
printf ' %s %s\n' "$label" "$value"
fi
}
ui_spinner_cleanup() {
if [ -z "$UI_SPINNER_PID" ]; then
return
fi
kill "$UI_SPINNER_PID" 2>/dev/null || true
wait "$UI_SPINNER_PID" 2>/dev/null || true
UI_SPINNER_PID=""
printf '\r\033[K' >&2
}
ui_spinner_start() {
local message="$1"
if ! ui__color_enabled; then
echo "$message"
return
fi
ui_spinner_cleanup
(
local spin='|/-\'
local i=0
while true; do
ui__cyn
printf '\r %s %s' "${spin:i%4:1}" "$message"
ui__reset
i=$((i + 1))
sleep 0.12
done
) >&2 &
UI_SPINNER_PID=$!
}
ui_spinner_stop() {
local message="$1"
local status="${2:-0}"
if ! ui__color_enabled; then
return "$status"
fi
ui_spinner_cleanup
ui__glyph_status "$status" "$message"
return "$status"
}
ui_spinner_run() {
local message="$1"
shift
ui_spinner_start "$message"
"$@"
local status=$?
ui_spinner_stop "$message" "$status"
return "$status"
}
ui__finish_box() {
local title="$1"
local emoji="$2"
local path="$3"
local detail="$4"
if ui__color_enabled; then
ui__mag; ui__bold
printf '\n ╭───────────────────────────────────────────────────────────╮\n'
printf ' %-57s\n' "$title"
printf ' ╰───────────────────────────────────────────────────────────╯\n'
ui__reset
ui__grn; printf ' %s ' "$emoji"; ui__reset
echo "$path"
ui__dim; printf ' %s\n' "$detail"; ui__reset
else
echo "$title: $path"
echo "$detail"
fi
}
ui_finish() {
local path="$1"
local size="$2"
ui__finish_box "ARCHIVE READY" '📦' "$path" "$size bytes · tar -tzf $(printf '%q' "$path")"
}
ui_finish_dir() {
local path="$1"
local size="$2"
local files="$3"
ui__finish_box "RUN CAPTURE READY" '📂' "$path" "$size bytes · $files files · start with run-context.txt"
}
ui_finish_report() {
local path="$1"
local size="$2"
ui__finish_box "COMPARISON READY" '📊' "$path" "$size bytes · share with an LLM or open in your editor"
}