diff --git a/scripts/install/linux-portable.sh b/scripts/install/linux-portable.sh new file mode 100644 index 0000000..ebee632 --- /dev/null +++ b/scripts/install/linux-portable.sh @@ -0,0 +1,707 @@ +#!/usr/bin/env bash + +if [ -z "$BASH_VERSION" ]; then + exec bash "$0" "$@" +fi + +THEME_NAME="SpoTUI" +REPO_URL="https://github.com/SkenSMasteR/SpoTUI" +THEMES_DIR="$HOME/.config/spicetify/Themes" +THEME_PATH="$THEMES_DIR/$THEME_NAME" + +ESC=$'\033' +RESET="$ESC[0m" + +BLOCK_FULL=$'\xe2\x96\x88' +BLOCK_LOWER=$'\xe2\x96\x84' +BLOCK_UPPER=$'\xe2\x96\x80' +BLOCK_LEFT=$'\xe2\x96\x8c' + +get_rgb() { + local r=$1 g=$2 b=$3 + printf '%s[38;2;%d;%d;%dm' "$ESC" "$r" "$g" "$b" +} + +ORANGE_LIGHT=$(get_rgb 255 140 66) +ORANGE_DARK=$(get_rgb 224 123 57) +ORANGE_MID=$(get_rgb 240 131 61) +GREEN_ANSI=$(get_rgb 140 255 140) +SELECT_BG="${ESC}[48;2;255;140;66m${ESC}[38;2;0;0;0m" + +WHITE="$ESC[97m" +GRAY="$ESC[90m" +RED="$ESC[91m" +GREEN="$ESC[92m" +CYAN="$ESC[96m" + +PKG_REFRESH_ATTEMPTED=0 + +get_gradient_color() { + local index=$1 total=$2 + local r1=255 g1=140 b1=66 + local r2=224 g2=123 b2=57 + local scale=1000 + local t + if [ "$total" -le 1 ]; then + t=0 + else + t=$(( index * scale / (total - 1) )) + fi + local r g b + r=$(( r1 + (r2 - r1) * t / scale )) + g=$(( g1 + (g2 - g1) * t / scale )) + b=$(( b1 + (b2 - b1) * t / scale )) + get_rgb "$r" "$g" "$b" +} + +get_ascii_art_line() { + local out="$1" + out="${out//A/$BLOCK_FULL}" + out="${out//B/$BLOCK_LOWER}" + out="${out//C/$BLOCK_UPPER}" + out="${out//D/$BLOCK_LEFT}" + printf '%s' "$out" +} + +get_header_lines() { + local templates=( + " BAAAAAAAA BAAAAAAAB BAAAAAAAB AAA AAA AB BA " + " AAA AAA AAA AAA AAA AAA CAAAAAAAAAB AAA AAA AAA " + " AAA AC AAA AAA AAA AAA CAAACCAA AAA AAA AAAD " + " AAA AAA AAA AAA AAA AAA C AAA AAA AAAD " + "CAAAAAAAAAAA CAAAAAAAAAC AAA AAA AAA AAA AAA AAAD " + " AAA AAA AAA AAA AAA AAA AAA AAA " + " BA AAA AAA AAA AAA AAA AAA AAA AAA " + " BAAAAAAAAC BAAAAC CAAAAAAC BAAAAC AAAAAAAAC AC " + ) + HEADER_LINES=() + HEADER_LINES+=("") + local total=${#templates[@]} + local i line color + for ((i = 0; i < total; i++)); do + color=$(get_gradient_color "$i" "$total") + line=$(get_ascii_art_line "${templates[$i]}") + HEADER_LINES+=("${color}${line}${RESET}") + done + HEADER_LINES+=("") + HEADER_LINES+=("${ORANGE_MID} Spicetify Theme Manager${RESET}") + HEADER_LINES+=("${ORANGE_DARK} =============================================================${RESET}") + HEADER_LINES+=("") +} + +show_header() { + clear + get_header_lines + local line + for line in "${HEADER_LINES[@]}"; do + printf '%s\n' "$line" + done +} + +get_visible_length() { + local text="$1" + local stripped + stripped=$(printf '%s' "$text" | sed -E "s/${ESC}\[[0-9;]*m//g") + printf '%d' "${#stripped}" +} + +write_frame() { + local -n frame_lines=$1 + printf '%s[H' "$ESC" + local width + width=$(tput cols 2>/dev/null) + [ -z "$width" ] && width=80 + local line visible pad + for line in "${frame_lines[@]}"; do + visible=$(get_visible_length "$line") + pad=$((width - visible - 1)) + [ "$pad" -lt 0 ] && pad=0 + printf '%s%*s\n' "$line" "$pad" "" + done +} + +get_console_height() { + local height + height=$(tput lines 2>/dev/null) + [ -z "$height" ] && height=30 + printf '%d' "$height" +} + +refresh_path() { + local candidate + for candidate in "$HOME/.local/bin" "$HOME/.spicetify" "/usr/local/bin"; do + if [[ ":$PATH:" != *":$candidate:"* ]] && [ -d "$candidate" ]; then + PATH="$candidate:$PATH" + fi + done + export PATH +} + +detect_pkg_manager() { + if command -v apt-get >/dev/null 2>&1; then echo "apt" + elif command -v dnf >/dev/null 2>&1; then echo "dnf" + elif command -v pacman >/dev/null 2>&1; then echo "pacman" + elif command -v zypper >/dev/null 2>&1; then echo "zypper" + elif command -v apk >/dev/null 2>&1; then echo "apk" + else echo "" + fi +} + +refresh_pkg_manager() { + local mgr + mgr=$(detect_pkg_manager) + printf '%s Refreshing package lists...%s\n' "$ORANGE_MID" "$RESET" + case "$mgr" in + apt) sudo apt-get update -y ;; + dnf) sudo dnf check-update -y || true ;; + pacman) sudo pacman -Sy --noconfirm ;; + zypper) sudo zypper refresh ;; + apk) sudo apk update ;; + *) + printf ' No supported package manager found (looked for apt, dnf, pacman, zypper, apk).%s\n' "" \ + 2>/dev/null + printf '%s No supported package manager found. Install git manually.%s\n' "$RED" "$RESET" + ;; + esac +} + +install_dependency() { + local name="$1" + if [ "$name" = "git" ]; then + local mgr + mgr=$(detect_pkg_manager) + if [ -n "$mgr" ]; then + printf '%s Installing git via %s...%s\n' "$GRAY" "$mgr" "$RESET" + case "$mgr" in + apt) sudo apt-get install -y git ;; + dnf) sudo dnf install -y git ;; + pacman) sudo pacman -S --noconfirm git ;; + zypper) sudo zypper install -y git ;; + apk) sudo apk add git ;; + esac + local exit_code=$? + printf ' package manager exited with code %d\n' "$exit_code" + + if [ "$exit_code" -ne 0 ] && [ "$PKG_REFRESH_ATTEMPTED" -eq 0 ]; then + printf '%s Install failed. Refreshing package lists and retrying...%s\n' "$RED" "$RESET" + PKG_REFRESH_ATTEMPTED=1 + refresh_pkg_manager + + printf '%s Retrying git install...%s\n' "$GRAY" "$RESET" + case "$mgr" in + apt) sudo apt-get install -y git ;; + dnf) sudo dnf install -y git ;; + pacman) sudo pacman -S --noconfirm git ;; + zypper) sudo zypper install -y git ;; + apk) sudo apk add git ;; + esac + fi + + refresh_path + else + printf '%s No supported package manager was found. Install git manually for your distro.%s\n' "$RED" "$RESET" + fi + elif [ "$name" = "spicetify" ]; then + printf '%s Installing Spicetify...%s\n' "$GRAY" "$RESET" + curl -fsSL https://raw.githubusercontent.com/spicetify/cli/main/install.sh | sh + printf '%s Spicetify installer finished.%s\n' "$GRAY" "$RESET" + refresh_path + fi +} + +test_dependencies() { + local missing=() + command -v git >/dev/null 2>&1 || missing+=("git") + command -v spicetify >/dev/null 2>&1 || missing+=("spicetify") + + if [ ${#missing[@]} -eq 0 ]; then + return 0 + fi + + printf '%s Missing dependencies: %s%s\n' "$RED" "$(IFS=', '; echo "${missing[*]}")" "$RESET" + echo "" + read -rp " Press I to install them now, or any other key to cancel: " confirm + if [ "$confirm" != "I" ] && [ "$confirm" != "i" ]; then + return 1 + fi + + local dep + for dep in "${missing[@]}"; do + echo "" + printf '%s Installing %s...%s\n' "$ORANGE_MID" "$dep" "$RESET" + install_dependency "$dep" + done + + echo "" + printf '%s Refreshing environment PATH...%s\n' "$ORANGE_MID" "$RESET" + refresh_path + + local still_missing=() + command -v git >/dev/null 2>&1 || still_missing+=("git") + command -v spicetify >/dev/null 2>&1 || still_missing+=("spicetify") + + if [ ${#still_missing[@]} -gt 0 ]; then + echo "" + printf '%s Still missing: %s. You may need to restart your terminal.%s\n' "$RED" "$(IFS=', '; echo "${still_missing[*]}")" "$RESET" + return 1 + fi + + echo "" + printf '%s All dependencies installed successfully.%s\n' "$GREEN" "$RESET" + return 0 +} + +get_default_branch() { + local ref + ref=$(git -C "$THEME_PATH" symbolic-ref refs/remotes/origin/HEAD 2>/dev/null) + if [ -n "$ref" ]; then + printf '%s' "${ref##*/}" + else + printf 'main' + fi +} + +test_is_detached() { + if git -C "$THEME_PATH" symbolic-ref -q HEAD >/dev/null 2>&1; then + return 1 + else + return 0 + fi +} + +get_theme_status_text="" +get_theme_status_color="" + +get_theme_status_detailed() { + if [ ! -d "$THEME_PATH" ]; then + get_theme_status_text="Not Installed" + get_theme_status_color="$RED" + return + fi + + if ! command -v git >/dev/null 2>&1; then + get_theme_status_text="Installed" + get_theme_status_color="$GREEN" + return + fi + + git -C "$THEME_PATH" fetch origin --quiet 2>/dev/null + local local_hash + local_hash=$(git -C "$THEME_PATH" rev-parse HEAD 2>/dev/null) + + if test_is_detached; then + local short_hash="unknown" + [ -n "$local_hash" ] && short_hash="${local_hash:0:7}" + get_theme_status_text="Installed (custom commit $short_hash)" + get_theme_status_color="$CYAN" + return + fi + + local branch remote_hash + branch=$(get_default_branch) + remote_hash=$(git -C "$THEME_PATH" rev-parse "origin/$branch" 2>/dev/null) + + if [ -z "$local_hash" ] || [ -z "$remote_hash" ]; then + get_theme_status_text="Installed" + get_theme_status_color="$GREEN" + return + fi + + if [ "$local_hash" = "$remote_hash" ]; then + get_theme_status_text="Installed (up to date)" + get_theme_status_color="$GREEN" + else + get_theme_status_text="Installed (outdated)" + get_theme_status_color="$RED" + fi +} + +install_theme() { + show_header + printf '%s Installing %s...%s\n' "$ORANGE_LIGHT" "$THEME_NAME" "$RESET" + echo "" + + if ! test_dependencies; then + pause_return + return + fi + + mkdir -p "$THEMES_DIR" + + if [ -d "$THEME_PATH" ]; then + printf '%s Theme already exists locally. Pulling latest changes...%s\n' "$ORANGE_MID" "$RESET" + git -C "$THEME_PATH" pull + else + (cd "$THEMES_DIR" && git clone "$REPO_URL" "$THEME_NAME") + fi + + if [ -d "$THEME_PATH" ]; then + echo "" + printf '%s Setting current theme to %s...%s\n' "$ORANGE_MID" "$THEME_NAME" "$RESET" + spicetify config current_theme "$THEME_NAME" + + printf '%s Applying Spicetify...%s\n' "$ORANGE_MID" "$RESET" + spicetify apply + + echo "" + printf '%s %s installed and applied successfully.%s\n' "$GREEN" "$THEME_NAME" "$RESET" + else + echo "" + printf '%s Installation failed. Check the errors above.%s\n' "$RED" "$RESET" + fi + + pause_return +} + +update_theme() { + show_header + printf '%s Updating %s...%s\n' "$ORANGE_LIGHT" "$THEME_NAME" "$RESET" + echo "" + + if [ ! -d "$THEME_PATH" ]; then + printf '%s %s is not installed. Use Install instead.%s\n' "$RED" "$THEME_NAME" "$RESET" + pause_return + return + fi + + if ! test_dependencies; then + pause_return + return + fi + + if test_is_detached; then + local branch + branch=$(get_default_branch) + printf '%s Currently on a custom commit. Returning to %s...%s\n' "$ORANGE_MID" "$branch" "$RESET" + git -C "$THEME_PATH" checkout "$branch" --quiet + fi + + git -C "$THEME_PATH" pull + + echo "" + printf '%s Re-applying Spicetify...%s\n' "$ORANGE_MID" "$RESET" + spicetify apply + + echo "" + printf '%s %s updated successfully.%s\n' "$GREEN" "$THEME_NAME" "$RESET" + pause_return +} + +uninstall_theme() { + show_header + printf '%s Uninstalling %s...%s\n' "$ORANGE_LIGHT" "$THEME_NAME" "$RESET" + echo "" + + if [ ! -d "$THEME_PATH" ]; then + printf '%s %s is not installed.%s\n' "$RED" "$THEME_NAME" "$RESET" + pause_return + return + fi + + printf '%s This will remove the theme folder and switch to Marketplace.%s\n' "$GRAY" "$RESET" + read -rp " Type Y to confirm: " confirm + if [ "$confirm" != "Y" ] && [ "$confirm" != "y" ]; then + printf '%s Cancelled.%s\n' "$GRAY" "$RESET" + pause_return + return + fi + + if command -v spicetify >/dev/null 2>&1; then + printf '%s Switching Spicetify theme...%s\n' "$ORANGE_MID" "$RESET" + spicetify config current_theme "SpoTUI-" + spicetify config current_theme marketplace + spicetify apply + fi + + rm -rf "$THEME_PATH" + + echo "" + printf '%s %s has been uninstalled.%s\n' "$GREEN" "$THEME_NAME" "$RESET" + pause_return +} + +read_key() { + local key rest + IFS= read -rsn1 key + if [ "$key" = "$ESC" ]; then + read -rsn2 -t 0.01 rest + case "$rest" in + "[A") echo "UP" ;; + "[B") echo "DOWN" ;; + *) echo "ESC" ;; + esac + elif [ -z "$key" ]; then + echo "ENTER" + else + echo "OTHER" + fi +} + +read_arrow_selection() { + local -n out_result=$1 + local -n _ras_items=$2 + local current_index=$3 + local -n _ras_title_lines=$4 + + local selected_index=0 + [ "$current_index" -ge 0 ] && selected_index=$current_index + + get_header_lines + local overhead=$(( ${#HEADER_LINES[@]} + ${#_ras_title_lines[@]} + 4 )) + + local page_size + page_size=$(( $(get_console_height) - overhead )) + [ "$page_size" -gt "${#_ras_items[@]}" ] && page_size=${#_ras_items[@]} + [ "$page_size" -lt 1 ] && page_size=1 + + clear + printf '%s[?25l' "$ESC" + + while true; do + local total_pages=$(( (${#_ras_items[@]} + page_size - 1) / page_size )) + [ "$total_pages" -lt 1 ] && total_pages=1 + local current_page=$(( selected_index / page_size )) + local page_start=$(( current_page * page_size )) + local page_end=$(( page_start + page_size - 1 )) + [ "$page_end" -gt $((${#_ras_items[@]} - 1)) ] && page_end=$((${#_ras_items[@]} - 1)) + + local frame=() + frame+=("${HEADER_LINES[@]}") + frame+=("${_ras_title_lines[@]}") + + local i prefix text + for ((i = page_start; i <= page_end; i++)); do + if [ "$i" -eq "$current_index" ]; then + prefix="> " + else + prefix=" " + fi + text="${prefix}${_ras_items[$i]}" + if [ "$i" -eq "$selected_index" ]; then + frame+=("${SELECT_BG}${text}${RESET}") + elif [ "$i" -eq "$current_index" ]; then + frame+=("${GREEN_ANSI}${text}${RESET}") + else + frame+=("$text") + fi + done + + local lines_used=$(( page_end - page_start + 1 )) + local p + for ((p = lines_used; p < page_size; p++)); do + frame+=("") + done + + frame+=("") + frame+=("${ORANGE_DARK} =============================================================${RESET}") + frame+=(" Up/Down to move, Enter to select, Esc to go back Page $((current_page + 1)) of $total_pages") + + write_frame frame + + local key + key=$(read_key) + + case "$key" in + UP) + if [ "$selected_index" -gt 0 ]; then + selected_index=$((selected_index - 1)) + else + selected_index=$((${#_ras_items[@]} - 1)) + fi + ;; + DOWN) + if [ "$selected_index" -lt $((${#_ras_items[@]} - 1)) ]; then + selected_index=$((selected_index + 1)) + else + selected_index=0 + fi + ;; + ENTER) + printf '%s[?25h' "$ESC" + out_result=$selected_index + return + ;; + ESC) + printf '%s[?25h' "$ESC" + out_result=-1 + return + ;; + esac + done +} + +get_commit_list() { + git -C "$THEME_PATH" fetch origin --quiet 2>/dev/null + COMMIT_FULL=() + COMMIT_SHORT=() + COMMIT_DATE=() + COMMIT_SUBJECT=() + local line full short date subject + while IFS='|' read -r full short date subject; do + [ -z "$full" ] && continue + COMMIT_FULL+=("$full") + COMMIT_SHORT+=("$short") + COMMIT_DATE+=("$date") + COMMIT_SUBJECT+=("$subject") + done < <(git -C "$THEME_PATH" log --all --pretty=format:"%H|%h|%ad|%s" --date=short) +} + +show_commit_history() { + if [ ! -d "$THEME_PATH" ]; then + show_header + printf '%s %s is not installed.%s\n' "$RED" "$THEME_NAME" "$RESET" + pause_return + return + fi + + if ! test_dependencies; then + pause_return + return + fi + + local viewing=1 + while [ "$viewing" -eq 1 ]; do + get_commit_list + if [ ${#COMMIT_FULL[@]} -eq 0 ]; then + show_header + printf '%s No commits found.%s\n' "$RED" "$RESET" + pause_return + return + fi + + local current_hash + current_hash=$(git -C "$THEME_PATH" rev-parse HEAD 2>/dev/null) + + local current_index=-1 + local items=() + local i + for ((i = 0; i < ${#COMMIT_FULL[@]}; i++)); do + items+=("${COMMIT_SHORT[$i]} ${COMMIT_DATE[$i]} ${COMMIT_SUBJECT[$i]}") + if [ "${COMMIT_FULL[$i]}" = "$current_hash" ]; then + current_index=$i + fi + done + local return_latest_index=${#items[@]} + items+=("Return to latest version") + local back_index=${#items[@]} + items+=("Back") + + local title_lines=("${ORANGE_LIGHT} Commit History${RESET}" "") + + local selection + read_arrow_selection selection items "$current_index" title_lines + + if [ "$selection" -eq -1 ] || [ "$selection" -eq "$back_index" ]; then + viewing=0 + elif [ "$selection" -eq "$return_latest_index" ]; then + update_theme + elif [ "$selection" -ge 0 ] && [ "$selection" -lt ${#COMMIT_FULL[@]} ]; then + checkout_commit "${COMMIT_FULL[$selection]}" "${COMMIT_SHORT[$selection]}" "${COMMIT_DATE[$selection]}" "${COMMIT_SUBJECT[$selection]}" + fi + done +} + +checkout_commit() { + local full="$1" short="$2" date="$3" subject="$4" + show_header + printf '%s Checking out commit %s...%s\n' "$ORANGE_LIGHT" "$short" "$RESET" + printf '%s %s %s%s\n' "$GRAY" "$date" "$subject" "$RESET" + echo "" + printf '%s This will switch the theme to this specific version.%s\n' "$GRAY" "$RESET" + read -rp " Type Y to confirm: " confirm + if [ "$confirm" != "Y" ] && [ "$confirm" != "y" ]; then + printf '%s Cancelled.%s\n' "$GRAY" "$RESET" + pause_return + return + fi + + git -C "$THEME_PATH" checkout "$full" --quiet + + if command -v spicetify >/dev/null 2>&1; then + echo "" + printf '%s Applying Spicetify...%s\n' "$ORANGE_MID" "$RESET" + spicetify apply + fi + + echo "" + printf '%s %s is now on commit %s.%s\n' "$GREEN" "$THEME_NAME" "$short" "$RESET" + pause_return +} + +check_for_updates() { + show_header + printf '%s Checking for updates...%s\n' "$ORANGE_LIGHT" "$RESET" + echo "" + + if [ ! -d "$THEME_PATH" ]; then + printf '%s %s is not installed.%s\n' "$RED" "$THEME_NAME" "$RESET" + pause_return + return + fi + + if ! test_dependencies; then + pause_return + return + fi + + get_theme_status_detailed + printf '%s Status: %s%s%s\n' "$WHITE" "$get_theme_status_color" "$get_theme_status_text" "$RESET" + + if [ "$get_theme_status_text" = "Installed (outdated)" ]; then + echo "" + printf '%s A newer version is available.%s\n' "$GRAY" "$RESET" + read -rp " Type Y to update now: " confirm + if [ "$confirm" = "Y" ] || [ "$confirm" = "y" ]; then + update_theme + return + fi + fi + + pause_return +} + +pause_return() { + echo "" + printf '%s Press any key to return to the menu...%s\n' "$GRAY" "$RESET" + IFS= read -rsn1 +} + +show_menu() { + show_header + get_theme_status_detailed + + printf '%s Status: %s%s%s\n' "$WHITE" "$get_theme_status_color" "$get_theme_status_text" "$RESET" + echo "" + printf '%s [1] Install %s%s\n' "$WHITE" "$THEME_NAME" "$RESET" + printf '%s [2] Update %s%s\n' "$WHITE" "$THEME_NAME" "$RESET" + printf '%s [3] Uninstall %s%s\n' "$WHITE" "$THEME_NAME" "$RESET" + printf '%s [4] Commit History / Downgrade%s\n' "$WHITE" "$RESET" + printf '%s [5] Check for Updates%s\n' "$WHITE" "$RESET" + printf '%s [6] Exit%s\n' "$WHITE" "$RESET" + echo "" + printf '%s =============================================================%s\n' "$ORANGE_DARK" "$RESET" + echo "" + read -rp " Select an option: " choice + MENU_CHOICE="$choice" +} + +running=1 +while [ "$running" -eq 1 ]; do + show_menu + choice="$MENU_CHOICE" + case "$choice" in + 1) install_theme ;; + 2) update_theme ;; + 3) uninstall_theme ;; + 4) show_commit_history ;; + 5) check_for_updates ;; + 6) running=0 ;; + *) + show_header + printf '%s Invalid option.%s\n' "$RED" "$RESET" + pause_return + ;; + esac +done + +clear \ No newline at end of file diff --git a/scripts/install/linux/install.sh b/scripts/install/linux/install.sh index ebee632..f7d9303 100644 --- a/scripts/install/linux/install.sh +++ b/scripts/install/linux/install.sh @@ -1,69 +1,52 @@ #!/usr/bin/env bash +grep -q $'\r' "$0" 2>/dev/null && tr -d '\r' < "$0" > "$0.lf" && chmod +x "$0.lf" && exec bash "$0.lf" "$@" -if [ -z "$BASH_VERSION" ]; then - exec bash "$0" "$@" -fi +Esc=$'\033' +Reset="${Esc}[0m" -THEME_NAME="SpoTUI" -REPO_URL="https://github.com/SkenSMasteR/SpoTUI" -THEMES_DIR="$HOME/.config/spicetify/Themes" -THEME_PATH="$THEMES_DIR/$THEME_NAME" - -ESC=$'\033' -RESET="$ESC[0m" - -BLOCK_FULL=$'\xe2\x96\x88' -BLOCK_LOWER=$'\xe2\x96\x84' -BLOCK_UPPER=$'\xe2\x96\x80' -BLOCK_LEFT=$'\xe2\x96\x8c' +BlockFull=$'\u2588' +BlockLower=$'\u2584' +BlockUpper=$'\u2580' +BlockLeft=$'\u258C' get_rgb() { local r=$1 g=$2 b=$3 - printf '%s[38;2;%d;%d;%dm' "$ESC" "$r" "$g" "$b" + echo "${Esc}[38;2;${r};${g};${b}m" } -ORANGE_LIGHT=$(get_rgb 255 140 66) -ORANGE_DARK=$(get_rgb 224 123 57) -ORANGE_MID=$(get_rgb 240 131 61) -GREEN_ANSI=$(get_rgb 140 255 140) -SELECT_BG="${ESC}[48;2;255;140;66m${ESC}[38;2;0;0;0m" - -WHITE="$ESC[97m" -GRAY="$ESC[90m" -RED="$ESC[91m" -GREEN="$ESC[92m" -CYAN="$ESC[96m" - -PKG_REFRESH_ATTEMPTED=0 +OrangeLight=$(get_rgb 255 140 66) +OrangeDark=$(get_rgb 224 123 57) +OrangeMid=$(get_rgb 240 131 61) +GreenAnsi=$(get_rgb 140 255 140) +RedAnsi=$(get_rgb 255 110 110) +GrayAnsi=$(get_rgb 130 130 130) get_gradient_color() { local index=$1 total=$2 local r1=255 g1=140 b1=66 local r2=224 g2=123 b2=57 - local scale=1000 - local t - if [ "$total" -le 1 ]; then - t=0 - else - t=$(( index * scale / (total - 1) )) + local t=0 + if [ "$total" -gt 1 ]; then + t=$(awk -v i="$index" -v t="$total" 'BEGIN { printf "%.6f", i/(t-1) }') fi local r g b - r=$(( r1 + (r2 - r1) * t / scale )) - g=$(( g1 + (g2 - g1) * t / scale )) - b=$(( b1 + (b2 - b1) * t / scale )) + r=$(awk -v r1="$r1" -v r2="$r2" -v t="$t" 'BEGIN { printf "%d", r1+(r2-r1)*t }') + g=$(awk -v g1="$g1" -v g2="$g2" -v t="$t" 'BEGIN { printf "%d", g1+(g2-g1)*t }') + b=$(awk -v b1="$b1" -v b2="$b2" -v t="$t" 'BEGIN { printf "%d", b1+(b2-b1)*t }') get_rgb "$r" "$g" "$b" } get_ascii_art_line() { local out="$1" - out="${out//A/$BLOCK_FULL}" - out="${out//B/$BLOCK_LOWER}" - out="${out//C/$BLOCK_UPPER}" - out="${out//D/$BLOCK_LEFT}" - printf '%s' "$out" + out="${out//A/$BlockFull}" + out="${out//B/$BlockLower}" + out="${out//C/$BlockUpper}" + out="${out//D/$BlockLeft}" + echo "$out" } -get_header_lines() { +show_header() { + clear local templates=( " BAAAAAAAA BAAAAAAAB BAAAAAAAB AAA AAA AB BA " " AAA AAA AAA AAA AAA AAA CAAAAAAAAAB AAA AAA AAA " @@ -74,634 +57,123 @@ get_header_lines() { " BA AAA AAA AAA AAA AAA AAA AAA AAA " " BAAAAAAAAC BAAAAC CAAAAAAC BAAAAC AAAAAAAAC AC " ) - HEADER_LINES=() - HEADER_LINES+=("") + echo "" local total=${#templates[@]} - local i line color - for ((i = 0; i < total; i++)); do + local i=0 + for tmpl in "${templates[@]}"; do + local color color=$(get_gradient_color "$i" "$total") - line=$(get_ascii_art_line "${templates[$i]}") - HEADER_LINES+=("${color}${line}${RESET}") + local line + line=$(get_ascii_art_line "$tmpl") + echo "${color}${line}${Reset}" + i=$((i + 1)) done - HEADER_LINES+=("") - HEADER_LINES+=("${ORANGE_MID} Spicetify Theme Manager${RESET}") - HEADER_LINES+=("${ORANGE_DARK} =============================================================${RESET}") - HEADER_LINES+=("") -} - -show_header() { - clear - get_header_lines - local line - for line in "${HEADER_LINES[@]}"; do - printf '%s\n' "$line" - done -} - -get_visible_length() { - local text="$1" - local stripped - stripped=$(printf '%s' "$text" | sed -E "s/${ESC}\[[0-9;]*m//g") - printf '%d' "${#stripped}" -} - -write_frame() { - local -n frame_lines=$1 - printf '%s[H' "$ESC" - local width - width=$(tput cols 2>/dev/null) - [ -z "$width" ] && width=80 - local line visible pad - for line in "${frame_lines[@]}"; do - visible=$(get_visible_length "$line") - pad=$((width - visible - 1)) - [ "$pad" -lt 0 ] && pad=0 - printf '%s%*s\n' "$line" "$pad" "" - done -} - -get_console_height() { - local height - height=$(tput lines 2>/dev/null) - [ -z "$height" ] && height=30 - printf '%d' "$height" -} - -refresh_path() { - local candidate - for candidate in "$HOME/.local/bin" "$HOME/.spicetify" "/usr/local/bin"; do - if [[ ":$PATH:" != *":$candidate:"* ]] && [ -d "$candidate" ]; then - PATH="$candidate:$PATH" - fi - done - export PATH -} - -detect_pkg_manager() { - if command -v apt-get >/dev/null 2>&1; then echo "apt" - elif command -v dnf >/dev/null 2>&1; then echo "dnf" - elif command -v pacman >/dev/null 2>&1; then echo "pacman" - elif command -v zypper >/dev/null 2>&1; then echo "zypper" - elif command -v apk >/dev/null 2>&1; then echo "apk" - else echo "" - fi -} - -refresh_pkg_manager() { - local mgr - mgr=$(detect_pkg_manager) - printf '%s Refreshing package lists...%s\n' "$ORANGE_MID" "$RESET" - case "$mgr" in - apt) sudo apt-get update -y ;; - dnf) sudo dnf check-update -y || true ;; - pacman) sudo pacman -Sy --noconfirm ;; - zypper) sudo zypper refresh ;; - apk) sudo apk update ;; - *) - printf ' No supported package manager found (looked for apt, dnf, pacman, zypper, apk).%s\n' "" \ - 2>/dev/null - printf '%s No supported package manager found. Install git manually.%s\n' "$RED" "$RESET" - ;; - esac -} - -install_dependency() { - local name="$1" - if [ "$name" = "git" ]; then - local mgr - mgr=$(detect_pkg_manager) - if [ -n "$mgr" ]; then - printf '%s Installing git via %s...%s\n' "$GRAY" "$mgr" "$RESET" - case "$mgr" in - apt) sudo apt-get install -y git ;; - dnf) sudo dnf install -y git ;; - pacman) sudo pacman -S --noconfirm git ;; - zypper) sudo zypper install -y git ;; - apk) sudo apk add git ;; - esac - local exit_code=$? - printf ' package manager exited with code %d\n' "$exit_code" - - if [ "$exit_code" -ne 0 ] && [ "$PKG_REFRESH_ATTEMPTED" -eq 0 ]; then - printf '%s Install failed. Refreshing package lists and retrying...%s\n' "$RED" "$RESET" - PKG_REFRESH_ATTEMPTED=1 - refresh_pkg_manager - - printf '%s Retrying git install...%s\n' "$GRAY" "$RESET" - case "$mgr" in - apt) sudo apt-get install -y git ;; - dnf) sudo dnf install -y git ;; - pacman) sudo pacman -S --noconfirm git ;; - zypper) sudo zypper install -y git ;; - apk) sudo apk add git ;; - esac - fi - - refresh_path - else - printf '%s No supported package manager was found. Install git manually for your distro.%s\n' "$RED" "$RESET" - fi - elif [ "$name" = "spicetify" ]; then - printf '%s Installing Spicetify...%s\n' "$GRAY" "$RESET" - curl -fsSL https://raw.githubusercontent.com/spicetify/cli/main/install.sh | sh - printf '%s Spicetify installer finished.%s\n' "$GRAY" "$RESET" - refresh_path - fi -} - -test_dependencies() { - local missing=() - command -v git >/dev/null 2>&1 || missing+=("git") - command -v spicetify >/dev/null 2>&1 || missing+=("spicetify") - - if [ ${#missing[@]} -eq 0 ]; then - return 0 - fi - - printf '%s Missing dependencies: %s%s\n' "$RED" "$(IFS=', '; echo "${missing[*]}")" "$RESET" echo "" - read -rp " Press I to install them now, or any other key to cancel: " confirm - if [ "$confirm" != "I" ] && [ "$confirm" != "i" ]; then - return 1 - fi - - local dep - for dep in "${missing[@]}"; do - echo "" - printf '%s Installing %s...%s\n' "$ORANGE_MID" "$dep" "$RESET" - install_dependency "$dep" - done - + echo "${OrangeMid} Installer${Reset}" + echo "${OrangeDark} =============================================================${Reset}" echo "" - printf '%s Refreshing environment PATH...%s\n' "$ORANGE_MID" "$RESET" - refresh_path - - local still_missing=() - command -v git >/dev/null 2>&1 || still_missing+=("git") - command -v spicetify >/dev/null 2>&1 || still_missing+=("spicetify") - - if [ ${#still_missing[@]} -gt 0 ]; then - echo "" - printf '%s Still missing: %s. You may need to restart your terminal.%s\n' "$RED" "$(IFS=', '; echo "${still_missing[*]}")" "$RESET" - return 1 - fi - - echo "" - printf '%s All dependencies installed successfully.%s\n' "$GREEN" "$RESET" - return 0 } -get_default_branch() { - local ref - ref=$(git -C "$THEME_PATH" symbolic-ref refs/remotes/origin/HEAD 2>/dev/null) - if [ -n "$ref" ]; then - printf '%s' "${ref##*/}" +SpinnerFrames=($'\u280B' $'\u2819' $'\u2839' $'\u2838' $'\u283C' $'\u2834' $'\u2826' $'\u2827' $'\u2807' $'\u280F') +CheckMark=$'\u2713' +CrossMark=$'\u2715' +CR=$'\r' + +invoke_step() { + local label="$1" + local workfn="$2" + + tput civis 2>/dev/null + + local tmpfile + tmpfile=$(mktemp) + "$workfn" >"$tmpfile" 2>&1 & + local pid=$! + + local frameIndex=0 + while kill -0 "$pid" 2>/dev/null; do + local frame="${SpinnerFrames[$((frameIndex % ${#SpinnerFrames[@]}))]}" + local color + color=$(get_gradient_color $((frameIndex % 8)) 8) + printf "%s %s%s%s %s" "$CR" "$color" "$frame" "$Reset" "$label" + sleep 0.08 + frameIndex=$((frameIndex + 1)) + done + + wait "$pid" + local status=$? + rm -f "$tmpfile" + + local pad + pad=$(printf ' %.0s' {1..40}) + if [ "$status" -ne 0 ]; then + printf "%s %s%s%s %s%s\n" "$CR" "$RedAnsi" "$CrossMark" "$Reset" "$label" "$pad" else - printf 'main' + printf "%s %s%s%s %s%s\n" "$CR" "$GreenAnsi" "$CheckMark" "$Reset" "$label" "$pad" fi + + tput cnorm 2>/dev/null + return $status } -test_is_detached() { - if git -C "$THEME_PATH" symbolic-ref -q HEAD >/dev/null 2>&1; then - return 1 - else - return 0 +show_header + +targetDir="$HOME/.local/share/spotui" +scriptPath="$targetDir/install.sh" +wrapperPath="$targetDir/spotui.sh" +binDir="$HOME/.local/bin" +binPath="$binDir/spotui" + +spotuiScriptContentBase64="IyEvdXNyL2Jpbi9lbnYgYmFzaA0KDQppZiBbIC16ICIkQkFTSF9WRVJTSU9OIiBdOyB0aGVuDQogICAgZXhlYyBiYXNoICIkMCIgIiRAIg0KZmkNCg0KVEhFTUVfTkFNRT0iU3BvVFVJIg0KUkVQT19VUkw9Imh0dHBzOi8vZ2l0aHViLmNvbS9Ta2VuU01hc3RlUi9TcG9UVUkiDQpUSEVNRVNfRElSPSIkSE9NRS8uY29uZmlnL3NwaWNldGlmeS9UaGVtZXMiDQpUSEVNRV9QQVRIPSIkVEhFTUVTX0RJUi8kVEhFTUVfTkFNRSINCg0KRVNDPSQnXDAzMycNClJFU0VUPSIkRVNDWzBtIg0KDQpCTE9DS19GVUxMPSQnXHhlMlx4OTZceDg4Jw0KQkxPQ0tfTE9XRVI9JCdceGUyXHg5Nlx4ODQnDQpCTE9DS19VUFBFUj0kJ1x4ZTJceDk2XHg4MCcNCkJMT0NLX0xFRlQ9JCdceGUyXHg5Nlx4OGMnDQoNCmdldF9yZ2IoKSB7DQogICAgbG9jYWwgcj0kMSBnPSQyIGI9JDMNCiAgICBwcmludGYgJyVzWzM4OzI7JWQ7JWQ7JWRtJyAiJEVTQyIgIiRyIiAiJGciICIkYiINCn0NCg0KT1JBTkdFX0xJR0hUPSQoZ2V0X3JnYiAyNTUgMTQwIDY2KQ0KT1JBTkdFX0RBUks9JChnZXRfcmdiIDIyNCAxMjMgNTcpDQpPUkFOR0VfTUlEPSQoZ2V0X3JnYiAyNDAgMTMxIDYxKQ0KR1JFRU5fQU5TST0kKGdldF9yZ2IgMTQwIDI1NSAxNDApDQpTRUxFQ1RfQkc9IiR7RVNDfVs0ODsyOzI1NTsxNDA7NjZtJHtFU0N9WzM4OzI7MDswOzBtIg0KDQpXSElURT0iJEVTQ1s5N20iDQpHUkFZPSIkRVNDWzkwbSINClJFRD0iJEVTQ1s5MW0iDQpHUkVFTj0iJEVTQ1s5Mm0iDQpDWUFOPSIkRVNDWzk2bSINCg0KUEtHX1JFRlJFU0hfQVRURU1QVEVEPTANCg0KZ2V0X2dyYWRpZW50X2NvbG9yKCkgew0KICAgIGxvY2FsIGluZGV4PSQxIHRvdGFsPSQyDQogICAgbG9jYWwgcjE9MjU1IGcxPTE0MCBiMT02Ng0KICAgIGxvY2FsIHIyPTIyNCBnMj0xMjMgYjI9NTcNCiAgICBsb2NhbCBzY2FsZT0xMDAwDQogICAgbG9jYWwgdA0KICAgIGlmIFsgIiR0b3RhbCIgLWxlIDEgXTsgdGhlbg0KICAgICAgICB0PTANCiAgICBlbHNlDQogICAgICAgIHQ9JCgoIGluZGV4ICogc2NhbGUgLyAodG90YWwgLSAxKSApKQ0KICAgIGZpDQogICAgbG9jYWwgciBnIGINCiAgICByPSQoKCByMSArIChyMiAtIHIxKSAqIHQgLyBzY2FsZSApKQ0KICAgIGc9JCgoIGcxICsgKGcyIC0gZzEpICogdCAvIHNjYWxlICkpDQogICAgYj0kKCggYjEgKyAoYjIgLSBiMSkgKiB0IC8gc2NhbGUgKSkNCiAgICBnZXRfcmdiICIkciIgIiRnIiAiJGIiDQp9DQoNCmdldF9hc2NpaV9hcnRfbGluZSgpIHsNCiAgICBsb2NhbCBvdXQ9IiQxIg0KICAgIG91dD0iJHtvdXQvL0EvJEJMT0NLX0ZVTEx9Ig0KICAgIG91dD0iJHtvdXQvL0IvJEJMT0NLX0xPV0VSfSINCiAgICBvdXQ9IiR7b3V0Ly9DLyRCTE9DS19VUFBFUn0iDQogICAgb3V0PSIke291dC8vRC8kQkxPQ0tfTEVGVH0iDQogICAgcHJpbnRmICclcycgIiRvdXQiDQp9DQoNCmdldF9oZWFkZXJfbGluZXMoKSB7DQogICAgbG9jYWwgdGVtcGxhdGVzPSgNCiAgICAgICAgIiAgIEJBQUFBQUFBQSAgICBCQUFBQUFBQUIgIEJBQUFBQUFBQiAgICAgIEFBQSAgICBBQUEgICAgQUIgICBCQSAgIg0KICAgICAgICAiICBBQUEgICAgQUFBICAgQUFBICAgIEFBQSBBQUEgICAgQUFBIENBQUFBQUFBQUFCIEFBQSAgICBBQUEgQUFBICAiDQogICAgICAgICIgIEFBQSAgICBBQyAgICBBQUEgICAgQUFBIEFBQSAgICBBQUEgICAgQ0FBQUNDQUEgQUFBICAgIEFBQSBBQUFEICINCiAgICAgICAgIiAgQUFBICAgICAgICAgIEFBQSAgICBBQUEgQUFBICAgIEFBQSAgICAgQUFBICAgQyBBQUEgICAgQUFBIEFBQUQgIg0KICAgICAgICAiQ0FBQUFBQUFBQUFBIENBQUFBQUFBQUFDICBBQUEgICAgQUFBICAgICBBQUEgICAgIEFBQSAgICBBQUEgQUFBRCAiDQogICAgICAgICIgICAgICAgICBBQUEgICBBQUEgICAgICAgIEFBQSAgICBBQUEgICAgIEFBQSAgICAgQUFBICAgIEFBQSBBQUEgICINCiAgICAgICAgIiAgIEJBICAgIEFBQSAgIEFBQSAgICAgICAgQUFBICAgIEFBQSAgICAgQUFBICAgICBBQUEgICAgQUFBIEFBQSAgIg0KICAgICAgICAiIEJBQUFBQUFBQUMgICBCQUFBQUMgICAgICAgQ0FBQUFBQUMgICAgIEJBQUFBQyAgIEFBQUFBQUFBQyAgQUMgICAiDQogICAgKQ0KICAgIEhFQURFUl9MSU5FUz0oKQ0KICAgIEhFQURFUl9MSU5FUys9KCIiKQ0KICAgIGxvY2FsIHRvdGFsPSR7I3RlbXBsYXRlc1tAXX0NCiAgICBsb2NhbCBpIGxpbmUgY29sb3INCiAgICBmb3IgKChpID0gMDsgaSA8IHRvdGFsOyBpKyspKTsgZG8NCiAgICAgICAgY29sb3I9JChnZXRfZ3JhZGllbnRfY29sb3IgIiRpIiAiJHRvdGFsIikNCiAgICAgICAgbGluZT0kKGdldF9hc2NpaV9hcnRfbGluZSAiJHt0ZW1wbGF0ZXNbJGldfSIpDQogICAgICAgIEhFQURFUl9MSU5FUys9KCIke2NvbG9yfSR7bGluZX0ke1JFU0VUfSIpDQogICAgZG9uZQ0KICAgIEhFQURFUl9MSU5FUys9KCIiKQ0KICAgIEhFQURFUl9MSU5FUys9KCIke09SQU5HRV9NSUR9ICAgICAgICAgICAgICAgICAgICAgU3BpY2V0aWZ5IFRoZW1lIE1hbmFnZXIke1JFU0VUfSIpDQogICAgSEVBREVSX0xJTkVTKz0oIiR7T1JBTkdFX0RBUkt9ICA9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09JHtSRVNFVH0iKQ0KICAgIEhFQURFUl9MSU5FUys9KCIiKQ0KfQ0KDQpzaG93X2hlYWRlcigpIHsNCiAgICBjbGVhcg0KICAgIGdldF9oZWFkZXJfbGluZXMNCiAgICBsb2NhbCBsaW5lDQogICAgZm9yIGxpbmUgaW4gIiR7SEVBREVSX0xJTkVTW0BdfSI7IGRvDQogICAgICAgIHByaW50ZiAnJXNcbicgIiRsaW5lIg0KICAgIGRvbmUNCn0NCg0KZ2V0X3Zpc2libGVfbGVuZ3RoKCkgew0KICAgIGxvY2FsIHRleHQ9IiQxIg0KICAgIGxvY2FsIHN0cmlwcGVkDQogICAgc3RyaXBwZWQ9JChwcmludGYgJyVzJyAiJHRleHQiIHwgc2VkIC1FICJzLyR7RVNDfVxbWzAtOTtdKm0vL2ciKQ0KICAgIHByaW50ZiAnJWQnICIkeyNzdHJpcHBlZH0iDQp9DQoNCndyaXRlX2ZyYW1lKCkgew0KICAgIGxvY2FsIC1uIGZyYW1lX2xpbmVzPSQxDQogICAgcHJpbnRmICclc1tIJyAiJEVTQyINCiAgICBsb2NhbCB3aWR0aA0KICAgIHdpZHRoPSQodHB1dCBjb2xzIDI+L2Rldi9udWxsKQ0KICAgIFsgLXogIiR3aWR0aCIgXSAmJiB3aWR0aD04MA0KICAgIGxvY2FsIGxpbmUgdmlzaWJsZSBwYWQNCiAgICBmb3IgbGluZSBpbiAiJHtmcmFtZV9saW5lc1tAXX0iOyBkbw0KICAgICAgICB2aXNpYmxlPSQoZ2V0X3Zpc2libGVfbGVuZ3RoICIkbGluZSIpDQogICAgICAgIHBhZD0kKCh3aWR0aCAtIHZpc2libGUgLSAxKSkNCiAgICAgICAgWyAiJHBhZCIgLWx0IDAgXSAmJiBwYWQ9MA0KICAgICAgICBwcmludGYgJyVzJSpzXG4nICIkbGluZSIgIiRwYWQiICIiDQogICAgZG9uZQ0KfQ0KDQpnZXRfY29uc29sZV9oZWlnaHQoKSB7DQogICAgbG9jYWwgaGVpZ2h0DQogICAgaGVpZ2h0PSQodHB1dCBsaW5lcyAyPi9kZXYvbnVsbCkNCiAgICBbIC16ICIkaGVpZ2h0IiBdICYmIGhlaWdodD0zMA0KICAgIHByaW50ZiAnJWQnICIkaGVpZ2h0Ig0KfQ0KDQpyZWZyZXNoX3BhdGgoKSB7DQogICAgbG9jYWwgY2FuZGlkYXRlDQogICAgZm9yIGNhbmRpZGF0ZSBpbiAiJEhPTUUvLmxvY2FsL2JpbiIgIiRIT01FLy5zcGljZXRpZnkiICIvdXNyL2xvY2FsL2JpbiI7IGRvDQogICAgICAgIGlmIFtbICI6JFBBVEg6IiAhPSAqIjokY2FuZGlkYXRlOiIqIF1dICYmIFsgLWQgIiRjYW5kaWRhdGUiIF07IHRoZW4NCiAgICAgICAgICAgIFBBVEg9IiRjYW5kaWRhdGU6JFBBVEgiDQogICAgICAgIGZpDQogICAgZG9uZQ0KICAgIGV4cG9ydCBQQVRIDQp9DQoNCmRldGVjdF9wa2dfbWFuYWdlcigpIHsNCiAgICBpZiBjb21tYW5kIC12IGFwdC1nZXQgPi9kZXYvbnVsbCAyPiYxOyB0aGVuIGVjaG8gImFwdCINCiAgICBlbGlmIGNvbW1hbmQgLXYgZG5mID4vZGV2L251bGwgMj4mMTsgdGhlbiBlY2hvICJkbmYiDQogICAgZWxpZiBjb21tYW5kIC12IHBhY21hbiA+L2Rldi9udWxsIDI+JjE7IHRoZW4gZWNobyAicGFjbWFuIg0KICAgIGVsaWYgY29tbWFuZCAtdiB6eXBwZXIgPi9kZXYvbnVsbCAyPiYxOyB0aGVuIGVjaG8gInp5cHBlciINCiAgICBlbGlmIGNvbW1hbmQgLXYgYXBrID4vZGV2L251bGwgMj4mMTsgdGhlbiBlY2hvICJhcGsiDQogICAgZWxzZSBlY2hvICIiDQogICAgZmkNCn0NCg0KcmVmcmVzaF9wa2dfbWFuYWdlcigpIHsNCiAgICBsb2NhbCBtZ3INCiAgICBtZ3I9JChkZXRlY3RfcGtnX21hbmFnZXIpDQogICAgcHJpbnRmICclcyAgUmVmcmVzaGluZyBwYWNrYWdlIGxpc3RzLi4uJXNcbicgIiRPUkFOR0VfTUlEIiAiJFJFU0VUIg0KICAgIGNhc2UgIiRtZ3IiIGluDQogICAgICAgIGFwdCkgICAgIHN1ZG8gYXB0LWdldCB1cGRhdGUgLXkgOzsNCiAgICAgICAgZG5mKSAgICAgc3VkbyBkbmYgY2hlY2stdXBkYXRlIC15IHx8IHRydWUgOzsNCiAgICAgICAgcGFjbWFuKSAgc3VkbyBwYWNtYW4gLVN5IC0tbm9jb25maXJtIDs7DQogICAgICAgIHp5cHBlcikgIHN1ZG8genlwcGVyIHJlZnJlc2ggOzsNCiAgICAgICAgYXBrKSAgICAgc3VkbyBhcGsgdXBkYXRlIDs7DQogICAgICAgICopDQogICAgICAgICAgICBwcmludGYgJyAgTm8gc3VwcG9ydGVkIHBhY2thZ2UgbWFuYWdlciBmb3VuZCAobG9va2VkIGZvciBhcHQsIGRuZiwgcGFjbWFuLCB6eXBwZXIsIGFwaykuJXNcbicgIiIgXA0KICAgICAgICAgICAgICAgIDI+L2Rldi9udWxsDQogICAgICAgICAgICBwcmludGYgJyVzICBObyBzdXBwb3J0ZWQgcGFja2FnZSBtYW5hZ2VyIGZvdW5kLiBJbnN0YWxsIGdpdCBtYW51YWxseS4lc1xuJyAiJFJFRCIgIiRSRVNFVCINCiAgICAgICAgICAgIDs7DQogICAgZXNhYw0KfQ0KDQppbnN0YWxsX2RlcGVuZGVuY3koKSB7DQogICAgbG9jYWwgbmFtZT0iJDEiDQogICAgaWYgWyAiJG5hbWUiID0gImdpdCIgXTsgdGhlbg0KICAgICAgICBsb2NhbCBtZ3INCiAgICAgICAgbWdyPSQoZGV0ZWN0X3BrZ19tYW5hZ2VyKQ0KICAgICAgICBpZiBbIC1uICIkbWdyIiBdOyB0aGVuDQogICAgICAgICAgICBwcmludGYgJyVzICBJbnN0YWxsaW5nIGdpdCB2aWEgJXMuLi4lc1xuJyAiJEdSQVkiICIkbWdyIiAiJFJFU0VUIg0KICAgICAgICAgICAgY2FzZSAiJG1nciIgaW4NCiAgICAgICAgICAgICAgICBhcHQpICAgICBzdWRvIGFwdC1nZXQgaW5zdGFsbCAteSBnaXQgOzsNCiAgICAgICAgICAgICAgICBkbmYpICAgICBzdWRvIGRuZiBpbnN0YWxsIC15IGdpdCA7Ow0KICAgICAgICAgICAgICAgIHBhY21hbikgIHN1ZG8gcGFjbWFuIC1TIC0tbm9jb25maXJtIGdpdCA7Ow0KICAgICAgICAgICAgICAgIHp5cHBlcikgIHN1ZG8genlwcGVyIGluc3RhbGwgLXkgZ2l0IDs7DQogICAgICAgICAgICAgICAgYXBrKSAgICAgc3VkbyBhcGsgYWRkIGdpdCA7Ow0KICAgICAgICAgICAgZXNhYw0KICAgICAgICAgICAgbG9jYWwgZXhpdF9jb2RlPSQ/DQogICAgICAgICAgICBwcmludGYgJyAgcGFja2FnZSBtYW5hZ2VyIGV4aXRlZCB3aXRoIGNvZGUgJWRcbicgIiRleGl0X2NvZGUiDQoNCiAgICAgICAgICAgIGlmIFsgIiRleGl0X2NvZGUiIC1uZSAwIF0gJiYgWyAiJFBLR19SRUZSRVNIX0FUVEVNUFRFRCIgLWVxIDAgXTsgdGhlbg0KICAgICAgICAgICAgICAgIHByaW50ZiAnJXMgIEluc3RhbGwgZmFpbGVkLiBSZWZyZXNoaW5nIHBhY2thZ2UgbGlzdHMgYW5kIHJldHJ5aW5nLi4uJXNcbicgIiRSRUQiICIkUkVTRVQiDQogICAgICAgICAgICAgICAgUEtHX1JFRlJFU0hfQVRURU1QVEVEPTENCiAgICAgICAgICAgICAgICByZWZyZXNoX3BrZ19tYW5hZ2VyDQoNCiAgICAgICAgICAgICAgICBwcmludGYgJyVzICBSZXRyeWluZyBnaXQgaW5zdGFsbC4uLiVzXG4nICIkR1JBWSIgIiRSRVNFVCINCiAgICAgICAgICAgICAgICBjYXNlICIkbWdyIiBpbg0KICAgICAgICAgICAgICAgICAgICBhcHQpICAgICBzdWRvIGFwdC1nZXQgaW5zdGFsbCAteSBnaXQgOzsNCiAgICAgICAgICAgICAgICAgICAgZG5mKSAgICAgc3VkbyBkbmYgaW5zdGFsbCAteSBnaXQgOzsNCiAgICAgICAgICAgICAgICAgICAgcGFjbWFuKSAgc3VkbyBwYWNtYW4gLVMgLS1ub2NvbmZpcm0gZ2l0IDs7DQogICAgICAgICAgICAgICAgICAgIHp5cHBlcikgIHN1ZG8genlwcGVyIGluc3RhbGwgLXkgZ2l0IDs7DQogICAgICAgICAgICAgICAgICAgIGFwaykgICAgIHN1ZG8gYXBrIGFkZCBnaXQgOzsNCiAgICAgICAgICAgICAgICBlc2FjDQogICAgICAgICAgICBmaQ0KDQogICAgICAgICAgICByZWZyZXNoX3BhdGgNCiAgICAgICAgZWxzZQ0KICAgICAgICAgICAgcHJpbnRmICclcyAgTm8gc3VwcG9ydGVkIHBhY2thZ2UgbWFuYWdlciB3YXMgZm91bmQuIEluc3RhbGwgZ2l0IG1hbnVhbGx5IGZvciB5b3VyIGRpc3Ryby4lc1xuJyAiJFJFRCIgIiRSRVNFVCINCiAgICAgICAgZmkNCiAgICBlbGlmIFsgIiRuYW1lIiA9ICJzcGljZXRpZnkiIF07IHRoZW4NCiAgICAgICAgcHJpbnRmICclcyAgSW5zdGFsbGluZyBTcGljZXRpZnkuLi4lc1xuJyAiJEdSQVkiICIkUkVTRVQiDQogICAgICAgIGN1cmwgLWZzU0wgaHR0cHM6Ly9yYXcuZ2l0aHVidXNlcmNvbnRlbnQuY29tL3NwaWNldGlmeS9jbGkvbWFpbi9pbnN0YWxsLnNoIHwgc2gNCiAgICAgICAgcHJpbnRmICclcyAgU3BpY2V0aWZ5IGluc3RhbGxlciBmaW5pc2hlZC4lc1xuJyAiJEdSQVkiICIkUkVTRVQiDQogICAgICAgIHJlZnJlc2hfcGF0aA0KICAgIGZpDQp9DQoNCnRlc3RfZGVwZW5kZW5jaWVzKCkgew0KICAgIGxvY2FsIG1pc3Npbmc9KCkNCiAgICBjb21tYW5kIC12IGdpdCA+L2Rldi9udWxsIDI+JjEgfHwgbWlzc2luZys9KCJnaXQiKQ0KICAgIGNvbW1hbmQgLXYgc3BpY2V0aWZ5ID4vZGV2L251bGwgMj4mMSB8fCBtaXNzaW5nKz0oInNwaWNldGlmeSIpDQoNCiAgICBpZiBbICR7I21pc3NpbmdbQF19IC1lcSAwIF07IHRoZW4NCiAgICAgICAgcmV0dXJuIDANCiAgICBmaQ0KDQogICAgcHJpbnRmICclcyAgTWlzc2luZyBkZXBlbmRlbmNpZXM6ICVzJXNcbicgIiRSRUQiICIkKElGUz0nLCAnOyBlY2hvICIke21pc3NpbmdbKl19IikiICIkUkVTRVQiDQogICAgZWNobyAiIg0KICAgIHJlYWQgLXJwICIgIFByZXNzIEkgdG8gaW5zdGFsbCB0aGVtIG5vdywgb3IgYW55IG90aGVyIGtleSB0byBjYW5jZWw6ICIgY29uZmlybQ0KICAgIGlmIFsgIiRjb25maXJtIiAhPSAiSSIgXSAmJiBbICIkY29uZmlybSIgIT0gImkiIF07IHRoZW4NCiAgICAgICAgcmV0dXJuIDENCiAgICBmaQ0KDQogICAgbG9jYWwgZGVwDQogICAgZm9yIGRlcCBpbiAiJHttaXNzaW5nW0BdfSI7IGRvDQogICAgICAgIGVjaG8gIiINCiAgICAgICAgcHJpbnRmICclcyAgSW5zdGFsbGluZyAlcy4uLiVzXG4nICIkT1JBTkdFX01JRCIgIiRkZXAiICIkUkVTRVQiDQogICAgICAgIGluc3RhbGxfZGVwZW5kZW5jeSAiJGRlcCINCiAgICBkb25lDQoNCiAgICBlY2hvICIiDQogICAgcHJpbnRmICclcyAgUmVmcmVzaGluZyBlbnZpcm9ubWVudCBQQVRILi4uJXNcbicgIiRPUkFOR0VfTUlEIiAiJFJFU0VUIg0KICAgIHJlZnJlc2hfcGF0aA0KDQogICAgbG9jYWwgc3RpbGxfbWlzc2luZz0oKQ0KICAgIGNvbW1hbmQgLXYgZ2l0ID4vZGV2L251bGwgMj4mMSB8fCBzdGlsbF9taXNzaW5nKz0oImdpdCIpDQogICAgY29tbWFuZCAtdiBzcGljZXRpZnkgPi9kZXYvbnVsbCAyPiYxIHx8IHN0aWxsX21pc3NpbmcrPSgic3BpY2V0aWZ5IikNCg0KICAgIGlmIFsgJHsjc3RpbGxfbWlzc2luZ1tAXX0gLWd0IDAgXTsgdGhlbg0KICAgICAgICBlY2hvICIiDQogICAgICAgIHByaW50ZiAnJXMgIFN0aWxsIG1pc3Npbmc6ICVzLiBZb3UgbWF5IG5lZWQgdG8gcmVzdGFydCB5b3VyIHRlcm1pbmFsLiVzXG4nICIkUkVEIiAiJChJRlM9JywgJzsgZWNobyAiJHtzdGlsbF9taXNzaW5nWypdfSIpIiAiJFJFU0VUIg0KICAgICAgICByZXR1cm4gMQ0KICAgIGZpDQoNCiAgICBlY2hvICIiDQogICAgcHJpbnRmICclcyAgQWxsIGRlcGVuZGVuY2llcyBpbnN0YWxsZWQgc3VjY2Vzc2Z1bGx5LiVzXG4nICIkR1JFRU4iICIkUkVTRVQiDQogICAgcmV0dXJuIDANCn0NCg0KZ2V0X2RlZmF1bHRfYnJhbmNoKCkgew0KICAgIGxvY2FsIHJlZg0KICAgIHJlZj0kKGdpdCAtQyAiJFRIRU1FX1BBVEgiIHN5bWJvbGljLXJlZiByZWZzL3JlbW90ZXMvb3JpZ2luL0hFQUQgMj4vZGV2L251bGwpDQogICAgaWYgWyAtbiAiJHJlZiIgXTsgdGhlbg0KICAgICAgICBwcmludGYgJyVzJyAiJHtyZWYjIyovfSINCiAgICBlbHNlDQogICAgICAgIHByaW50ZiAnbWFpbicNCiAgICBmaQ0KfQ0KDQp0ZXN0X2lzX2RldGFjaGVkKCkgew0KICAgIGlmIGdpdCAtQyAiJFRIRU1FX1BBVEgiIHN5bWJvbGljLXJlZiAtcSBIRUFEID4vZGV2L251bGwgMj4mMTsgdGhlbg0KICAgICAgICByZXR1cm4gMQ0KICAgIGVsc2UNCiAgICAgICAgcmV0dXJuIDANCiAgICBmaQ0KfQ0KDQpnZXRfdGhlbWVfc3RhdHVzX3RleHQ9IiINCmdldF90aGVtZV9zdGF0dXNfY29sb3I9IiINCg0KZ2V0X3RoZW1lX3N0YXR1c19kZXRhaWxlZCgpIHsNCiAgICBpZiBbICEgLWQgIiRUSEVNRV9QQVRIIiBdOyB0aGVuDQogICAgICAgIGdldF90aGVtZV9zdGF0dXNfdGV4dD0iTm90IEluc3RhbGxlZCINCiAgICAgICAgZ2V0X3RoZW1lX3N0YXR1c19jb2xvcj0iJFJFRCINCiAgICAgICAgcmV0dXJuDQogICAgZmkNCg0KICAgIGlmICEgY29tbWFuZCAtdiBnaXQgPi9kZXYvbnVsbCAyPiYxOyB0aGVuDQogICAgICAgIGdldF90aGVtZV9zdGF0dXNfdGV4dD0iSW5zdGFsbGVkIg0KICAgICAgICBnZXRfdGhlbWVfc3RhdHVzX2NvbG9yPSIkR1JFRU4iDQogICAgICAgIHJldHVybg0KICAgIGZpDQoNCiAgICBnaXQgLUMgIiRUSEVNRV9QQVRIIiBmZXRjaCBvcmlnaW4gLS1xdWlldCAyPi9kZXYvbnVsbA0KICAgIGxvY2FsIGxvY2FsX2hhc2gNCiAgICBsb2NhbF9oYXNoPSQoZ2l0IC1DICIkVEhFTUVfUEFUSCIgcmV2LXBhcnNlIEhFQUQgMj4vZGV2L251bGwpDQoNCiAgICBpZiB0ZXN0X2lzX2RldGFjaGVkOyB0aGVuDQogICAgICAgIGxvY2FsIHNob3J0X2hhc2g9InVua25vd24iDQogICAgICAgIFsgLW4gIiRsb2NhbF9oYXNoIiBdICYmIHNob3J0X2hhc2g9IiR7bG9jYWxfaGFzaDowOjd9Ig0KICAgICAgICBnZXRfdGhlbWVfc3RhdHVzX3RleHQ9Ikluc3RhbGxlZCAoY3VzdG9tIGNvbW1pdCAkc2hvcnRfaGFzaCkiDQogICAgICAgIGdldF90aGVtZV9zdGF0dXNfY29sb3I9IiRDWUFOIg0KICAgICAgICByZXR1cm4NCiAgICBmaQ0KDQogICAgbG9jYWwgYnJhbmNoIHJlbW90ZV9oYXNoDQogICAgYnJhbmNoPSQoZ2V0X2RlZmF1bHRfYnJhbmNoKQ0KICAgIHJlbW90ZV9oYXNoPSQoZ2l0IC1DICIkVEhFTUVfUEFUSCIgcmV2LXBhcnNlICJvcmlnaW4vJGJyYW5jaCIgMj4vZGV2L251bGwpDQoNCiAgICBpZiBbIC16ICIkbG9jYWxfaGFzaCIgXSB8fCBbIC16ICIkcmVtb3RlX2hhc2giIF07IHRoZW4NCiAgICAgICAgZ2V0X3RoZW1lX3N0YXR1c190ZXh0PSJJbnN0YWxsZWQiDQogICAgICAgIGdldF90aGVtZV9zdGF0dXNfY29sb3I9IiRHUkVFTiINCiAgICAgICAgcmV0dXJuDQogICAgZmkNCg0KICAgIGlmIFsgIiRsb2NhbF9oYXNoIiA9ICIkcmVtb3RlX2hhc2giIF07IHRoZW4NCiAgICAgICAgZ2V0X3RoZW1lX3N0YXR1c190ZXh0PSJJbnN0YWxsZWQgKHVwIHRvIGRhdGUpIg0KICAgICAgICBnZXRfdGhlbWVfc3RhdHVzX2NvbG9yPSIkR1JFRU4iDQogICAgZWxzZQ0KICAgICAgICBnZXRfdGhlbWVfc3RhdHVzX3RleHQ9Ikluc3RhbGxlZCAob3V0ZGF0ZWQpIg0KICAgICAgICBnZXRfdGhlbWVfc3RhdHVzX2NvbG9yPSIkUkVEIg0KICAgIGZpDQp9DQoNCmluc3RhbGxfdGhlbWUoKSB7DQogICAgc2hvd19oZWFkZXINCiAgICBwcmludGYgJyVzICBJbnN0YWxsaW5nICVzLi4uJXNcbicgIiRPUkFOR0VfTElHSFQiICIkVEhFTUVfTkFNRSIgIiRSRVNFVCINCiAgICBlY2hvICIiDQoNCiAgICBpZiAhIHRlc3RfZGVwZW5kZW5jaWVzOyB0aGVuDQogICAgICAgIHBhdXNlX3JldHVybg0KICAgICAgICByZXR1cm4NCiAgICBmaQ0KDQogICAgbWtkaXIgLXAgIiRUSEVNRVNfRElSIg0KDQogICAgaWYgWyAtZCAiJFRIRU1FX1BBVEgiIF07IHRoZW4NCiAgICAgICAgcHJpbnRmICclcyAgVGhlbWUgYWxyZWFkeSBleGlzdHMgbG9jYWxseS4gUHVsbGluZyBsYXRlc3QgY2hhbmdlcy4uLiVzXG4nICIkT1JBTkdFX01JRCIgIiRSRVNFVCINCiAgICAgICAgZ2l0IC1DICIkVEhFTUVfUEFUSCIgcHVsbA0KICAgIGVsc2UNCiAgICAgICAgKGNkICIkVEhFTUVTX0RJUiIgJiYgZ2l0IGNsb25lICIkUkVQT19VUkwiICIkVEhFTUVfTkFNRSIpDQogICAgZmkNCg0KICAgIGlmIFsgLWQgIiRUSEVNRV9QQVRIIiBdOyB0aGVuDQogICAgICAgIGVjaG8gIiINCiAgICAgICAgcHJpbnRmICclcyAgU2V0dGluZyBjdXJyZW50IHRoZW1lIHRvICVzLi4uJXNcbicgIiRPUkFOR0VfTUlEIiAiJFRIRU1FX05BTUUiICIkUkVTRVQiDQogICAgICAgIHNwaWNldGlmeSBjb25maWcgY3VycmVudF90aGVtZSAiJFRIRU1FX05BTUUiDQoNCiAgICAgICAgcHJpbnRmICclcyAgQXBwbHlpbmcgU3BpY2V0aWZ5Li4uJXNcbicgIiRPUkFOR0VfTUlEIiAiJFJFU0VUIg0KICAgICAgICBzcGljZXRpZnkgYXBwbHkNCg0KICAgICAgICBlY2hvICIiDQogICAgICAgIHByaW50ZiAnJXMgICVzIGluc3RhbGxlZCBhbmQgYXBwbGllZCBzdWNjZXNzZnVsbHkuJXNcbicgIiRHUkVFTiIgIiRUSEVNRV9OQU1FIiAiJFJFU0VUIg0KICAgIGVsc2UNCiAgICAgICAgZWNobyAiIg0KICAgICAgICBwcmludGYgJyVzICBJbnN0YWxsYXRpb24gZmFpbGVkLiBDaGVjayB0aGUgZXJyb3JzIGFib3ZlLiVzXG4nICIkUkVEIiAiJFJFU0VUIg0KICAgIGZpDQoNCiAgICBwYXVzZV9yZXR1cm4NCn0NCg0KdXBkYXRlX3RoZW1lKCkgew0KICAgIHNob3dfaGVhZGVyDQogICAgcHJpbnRmICclcyAgVXBkYXRpbmcgJXMuLi4lc1xuJyAiJE9SQU5HRV9MSUdIVCIgIiRUSEVNRV9OQU1FIiAiJFJFU0VUIg0KICAgIGVjaG8gIiINCg0KICAgIGlmIFsgISAtZCAiJFRIRU1FX1BBVEgiIF07IHRoZW4NCiAgICAgICAgcHJpbnRmICclcyAgJXMgaXMgbm90IGluc3RhbGxlZC4gVXNlIEluc3RhbGwgaW5zdGVhZC4lc1xuJyAiJFJFRCIgIiRUSEVNRV9OQU1FIiAiJFJFU0VUIg0KICAgICAgICBwYXVzZV9yZXR1cm4NCiAgICAgICAgcmV0dXJuDQogICAgZmkNCg0KICAgIGlmICEgdGVzdF9kZXBlbmRlbmNpZXM7IHRoZW4NCiAgICAgICAgcGF1c2VfcmV0dXJuDQogICAgICAgIHJldHVybg0KICAgIGZpDQoNCiAgICBpZiB0ZXN0X2lzX2RldGFjaGVkOyB0aGVuDQogICAgICAgIGxvY2FsIGJyYW5jaA0KICAgICAgICBicmFuY2g9JChnZXRfZGVmYXVsdF9icmFuY2gpDQogICAgICAgIHByaW50ZiAnJXMgIEN1cnJlbnRseSBvbiBhIGN1c3RvbSBjb21taXQuIFJldHVybmluZyB0byAlcy4uLiVzXG4nICIkT1JBTkdFX01JRCIgIiRicmFuY2giICIkUkVTRVQiDQogICAgICAgIGdpdCAtQyAiJFRIRU1FX1BBVEgiIGNoZWNrb3V0ICIkYnJhbmNoIiAtLXF1aWV0DQogICAgZmkNCg0KICAgIGdpdCAtQyAiJFRIRU1FX1BBVEgiIHB1bGwNCg0KICAgIGVjaG8gIiINCiAgICBwcmludGYgJyVzICBSZS1hcHBseWluZyBTcGljZXRpZnkuLi4lc1xuJyAiJE9SQU5HRV9NSUQiICIkUkVTRVQiDQogICAgc3BpY2V0aWZ5IGFwcGx5DQoNCiAgICBlY2hvICIiDQogICAgcHJpbnRmICclcyAgJXMgdXBkYXRlZCBzdWNjZXNzZnVsbHkuJXNcbicgIiRHUkVFTiIgIiRUSEVNRV9OQU1FIiAiJFJFU0VUIg0KICAgIHBhdXNlX3JldHVybg0KfQ0KDQp1bmluc3RhbGxfdGhlbWUoKSB7DQogICAgc2hvd19oZWFkZXINCiAgICBwcmludGYgJyVzICBVbmluc3RhbGxpbmcgJXMuLi4lc1xuJyAiJE9SQU5HRV9MSUdIVCIgIiRUSEVNRV9OQU1FIiAiJFJFU0VUIg0KICAgIGVjaG8gIiINCg0KICAgIGlmIFsgISAtZCAiJFRIRU1FX1BBVEgiIF07IHRoZW4NCiAgICAgICAgcHJpbnRmICclcyAgJXMgaXMgbm90IGluc3RhbGxlZC4lc1xuJyAiJFJFRCIgIiRUSEVNRV9OQU1FIiAiJFJFU0VUIg0KICAgICAgICBwYXVzZV9yZXR1cm4NCiAgICAgICAgcmV0dXJuDQogICAgZmkNCg0KICAgIHByaW50ZiAnJXMgIFRoaXMgd2lsbCByZW1vdmUgdGhlIHRoZW1lIGZvbGRlciBhbmQgc3dpdGNoIHRvIE1hcmtldHBsYWNlLiVzXG4nICIkR1JBWSIgIiRSRVNFVCINCiAgICByZWFkIC1ycCAiICBUeXBlIFkgdG8gY29uZmlybTogIiBjb25maXJtDQogICAgaWYgWyAiJGNvbmZpcm0iICE9ICJZIiBdICYmIFsgIiRjb25maXJtIiAhPSAieSIgXTsgdGhlbg0KICAgICAgICBwcmludGYgJyVzICBDYW5jZWxsZWQuJXNcbicgIiRHUkFZIiAiJFJFU0VUIg0KICAgICAgICBwYXVzZV9yZXR1cm4NCiAgICAgICAgcmV0dXJuDQogICAgZmkNCg0KICAgIGlmIGNvbW1hbmQgLXYgc3BpY2V0aWZ5ID4vZGV2L251bGwgMj4mMTsgdGhlbg0KICAgICAgICBwcmludGYgJyVzICBTd2l0Y2hpbmcgU3BpY2V0aWZ5IHRoZW1lLi4uJXNcbicgIiRPUkFOR0VfTUlEIiAiJFJFU0VUIg0KICAgICAgICBzcGljZXRpZnkgY29uZmlnIGN1cnJlbnRfdGhlbWUgIlNwb1RVSS0iDQogICAgICAgIHNwaWNldGlmeSBjb25maWcgY3VycmVudF90aGVtZSBtYXJrZXRwbGFjZQ0KICAgICAgICBzcGljZXRpZnkgYXBwbHkNCiAgICBmaQ0KDQogICAgcm0gLXJmICIkVEhFTUVfUEFUSCINCg0KICAgIGVjaG8gIiINCiAgICBwcmludGYgJyVzICAlcyBoYXMgYmVlbiB1bmluc3RhbGxlZC4lc1xuJyAiJEdSRUVOIiAiJFRIRU1FX05BTUUiICIkUkVTRVQiDQogICAgcGF1c2VfcmV0dXJuDQp9DQoNCnJlYWRfa2V5KCkgew0KICAgIGxvY2FsIGtleSByZXN0DQogICAgSUZTPSByZWFkIC1yc24xIGtleQ0KICAgIGlmIFsgIiRrZXkiID0gIiRFU0MiIF07IHRoZW4NCiAgICAgICAgcmVhZCAtcnNuMiAtdCAwLjAxIHJlc3QNCiAgICAgICAgY2FzZSAiJHJlc3QiIGluDQogICAgICAgICAgICAiW0EiKSBlY2hvICJVUCIgOzsNCiAgICAgICAgICAgICJbQiIpIGVjaG8gIkRPV04iIDs7DQogICAgICAgICAgICAqKSAgICBlY2hvICJFU0MiIDs7DQogICAgICAgIGVzYWMNCiAgICBlbGlmIFsgLXogIiRrZXkiIF07IHRoZW4NCiAgICAgICAgZWNobyAiRU5URVIiDQogICAgZWxzZQ0KICAgICAgICBlY2hvICJPVEhFUiINCiAgICBmaQ0KfQ0KDQpyZWFkX2Fycm93X3NlbGVjdGlvbigpIHsNCiAgICBsb2NhbCAtbiBvdXRfcmVzdWx0PSQxDQogICAgbG9jYWwgLW4gX3Jhc19pdGVtcz0kMg0KICAgIGxvY2FsIGN1cnJlbnRfaW5kZXg9JDMNCiAgICBsb2NhbCAtbiBfcmFzX3RpdGxlX2xpbmVzPSQ0DQoNCiAgICBsb2NhbCBzZWxlY3RlZF9pbmRleD0wDQogICAgWyAiJGN1cnJlbnRfaW5kZXgiIC1nZSAwIF0gJiYgc2VsZWN0ZWRfaW5kZXg9JGN1cnJlbnRfaW5kZXgNCg0KICAgIGdldF9oZWFkZXJfbGluZXMNCiAgICBsb2NhbCBvdmVyaGVhZD0kKCggJHsjSEVBREVSX0xJTkVTW0BdfSArICR7I19yYXNfdGl0bGVfbGluZXNbQF19ICsgNCApKQ0KDQogICAgbG9jYWwgcGFnZV9zaXplDQogICAgcGFnZV9zaXplPSQoKCAkKGdldF9jb25zb2xlX2hlaWdodCkgLSBvdmVyaGVhZCApKQ0KICAgIFsgIiRwYWdlX3NpemUiIC1ndCAiJHsjX3Jhc19pdGVtc1tAXX0iIF0gJiYgcGFnZV9zaXplPSR7I19yYXNfaXRlbXNbQF19DQogICAgWyAiJHBhZ2Vfc2l6ZSIgLWx0IDEgXSAmJiBwYWdlX3NpemU9MQ0KDQogICAgY2xlYXINCiAgICBwcmludGYgJyVzWz8yNWwnICIkRVNDIg0KDQogICAgd2hpbGUgdHJ1ZTsgZG8NCiAgICAgICAgbG9jYWwgdG90YWxfcGFnZXM9JCgoICgkeyNfcmFzX2l0ZW1zW0BdfSArIHBhZ2Vfc2l6ZSAtIDEpIC8gcGFnZV9zaXplICkpDQogICAgICAgIFsgIiR0b3RhbF9wYWdlcyIgLWx0IDEgXSAmJiB0b3RhbF9wYWdlcz0xDQogICAgICAgIGxvY2FsIGN1cnJlbnRfcGFnZT0kKCggc2VsZWN0ZWRfaW5kZXggLyBwYWdlX3NpemUgKSkNCiAgICAgICAgbG9jYWwgcGFnZV9zdGFydD0kKCggY3VycmVudF9wYWdlICogcGFnZV9zaXplICkpDQogICAgICAgIGxvY2FsIHBhZ2VfZW5kPSQoKCBwYWdlX3N0YXJ0ICsgcGFnZV9zaXplIC0gMSApKQ0KICAgICAgICBbICIkcGFnZV9lbmQiIC1ndCAkKCgkeyNfcmFzX2l0ZW1zW0BdfSAtIDEpKSBdICYmIHBhZ2VfZW5kPSQoKCR7I19yYXNfaXRlbXNbQF19IC0gMSkpDQoNCiAgICAgICAgbG9jYWwgZnJhbWU9KCkNCiAgICAgICAgZnJhbWUrPSgiJHtIRUFERVJfTElORVNbQF19IikNCiAgICAgICAgZnJhbWUrPSgiJHtfcmFzX3RpdGxlX2xpbmVzW0BdfSIpDQoNCiAgICAgICAgbG9jYWwgaSBwcmVmaXggdGV4dA0KICAgICAgICBmb3IgKChpID0gcGFnZV9zdGFydDsgaSA8PSBwYWdlX2VuZDsgaSsrKSk7IGRvDQogICAgICAgICAgICBpZiBbICIkaSIgLWVxICIkY3VycmVudF9pbmRleCIgXTsgdGhlbg0KICAgICAgICAgICAgICAgIHByZWZpeD0iPiAiDQogICAgICAgICAgICBlbHNlDQogICAgICAgICAgICAgICAgcHJlZml4PSIgICINCiAgICAgICAgICAgIGZpDQogICAgICAgICAgICB0ZXh0PSIke3ByZWZpeH0ke19yYXNfaXRlbXNbJGldfSINCiAgICAgICAgICAgIGlmIFsgIiRpIiAtZXEgIiRzZWxlY3RlZF9pbmRleCIgXTsgdGhlbg0KICAgICAgICAgICAgICAgIGZyYW1lKz0oIiR7U0VMRUNUX0JHfSR7dGV4dH0ke1JFU0VUfSIpDQogICAgICAgICAgICBlbGlmIFsgIiRpIiAtZXEgIiRjdXJyZW50X2luZGV4IiBdOyB0aGVuDQogICAgICAgICAgICAgICAgZnJhbWUrPSgiJHtHUkVFTl9BTlNJfSR7dGV4dH0ke1JFU0VUfSIpDQogICAgICAgICAgICBlbHNlDQogICAgICAgICAgICAgICAgZnJhbWUrPSgiJHRleHQiKQ0KICAgICAgICAgICAgZmkNCiAgICAgICAgZG9uZQ0KDQogICAgICAgIGxvY2FsIGxpbmVzX3VzZWQ9JCgoIHBhZ2VfZW5kIC0gcGFnZV9zdGFydCArIDEgKSkNCiAgICAgICAgbG9jYWwgcA0KICAgICAgICBmb3IgKChwID0gbGluZXNfdXNlZDsgcCA8IHBhZ2Vfc2l6ZTsgcCsrKSk7IGRvDQogICAgICAgICAgICBmcmFtZSs9KCIiKQ0KICAgICAgICBkb25lDQoNCiAgICAgICAgZnJhbWUrPSgiIikNCiAgICAgICAgZnJhbWUrPSgiJHtPUkFOR0VfREFSS30gID09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0ke1JFU0VUfSIpDQogICAgICAgIGZyYW1lKz0oIiAgVXAvRG93biB0byBtb3ZlLCBFbnRlciB0byBzZWxlY3QsIEVzYyB0byBnbyBiYWNrICAgUGFnZSAkKChjdXJyZW50X3BhZ2UgKyAxKSkgb2YgJHRvdGFsX3BhZ2VzIikNCg0KICAgICAgICB3cml0ZV9mcmFtZSBmcmFtZQ0KDQogICAgICAgIGxvY2FsIGtleQ0KICAgICAgICBrZXk9JChyZWFkX2tleSkNCg0KICAgICAgICBjYXNlICIka2V5IiBpbg0KICAgICAgICAgICAgVVApDQogICAgICAgICAgICAgICAgaWYgWyAiJHNlbGVjdGVkX2luZGV4IiAtZ3QgMCBdOyB0aGVuDQogICAgICAgICAgICAgICAgICAgIHNlbGVjdGVkX2luZGV4PSQoKHNlbGVjdGVkX2luZGV4IC0gMSkpDQogICAgICAgICAgICAgICAgZWxzZQ0KICAgICAgICAgICAgICAgICAgICBzZWxlY3RlZF9pbmRleD0kKCgkeyNfcmFzX2l0ZW1zW0BdfSAtIDEpKQ0KICAgICAgICAgICAgICAgIGZpDQogICAgICAgICAgICAgICAgOzsNCiAgICAgICAgICAgIERPV04pDQogICAgICAgICAgICAgICAgaWYgWyAiJHNlbGVjdGVkX2luZGV4IiAtbHQgJCgoJHsjX3Jhc19pdGVtc1tAXX0gLSAxKSkgXTsgdGhlbg0KICAgICAgICAgICAgICAgICAgICBzZWxlY3RlZF9pbmRleD0kKChzZWxlY3RlZF9pbmRleCArIDEpKQ0KICAgICAgICAgICAgICAgIGVsc2UNCiAgICAgICAgICAgICAgICAgICAgc2VsZWN0ZWRfaW5kZXg9MA0KICAgICAgICAgICAgICAgIGZpDQogICAgICAgICAgICAgICAgOzsNCiAgICAgICAgICAgIEVOVEVSKQ0KICAgICAgICAgICAgICAgIHByaW50ZiAnJXNbPzI1aCcgIiRFU0MiDQogICAgICAgICAgICAgICAgb3V0X3Jlc3VsdD0kc2VsZWN0ZWRfaW5kZXgNCiAgICAgICAgICAgICAgICByZXR1cm4NCiAgICAgICAgICAgICAgICA7Ow0KICAgICAgICAgICAgRVNDKQ0KICAgICAgICAgICAgICAgIHByaW50ZiAnJXNbPzI1aCcgIiRFU0MiDQogICAgICAgICAgICAgICAgb3V0X3Jlc3VsdD0tMQ0KICAgICAgICAgICAgICAgIHJldHVybg0KICAgICAgICAgICAgICAgIDs7DQogICAgICAgIGVzYWMNCiAgICBkb25lDQp9DQoNCmdldF9jb21taXRfbGlzdCgpIHsNCiAgICBnaXQgLUMgIiRUSEVNRV9QQVRIIiBmZXRjaCBvcmlnaW4gLS1xdWlldCAyPi9kZXYvbnVsbA0KICAgIENPTU1JVF9GVUxMPSgpDQogICAgQ09NTUlUX1NIT1JUPSgpDQogICAgQ09NTUlUX0RBVEU9KCkNCiAgICBDT01NSVRfU1VCSkVDVD0oKQ0KICAgIGxvY2FsIGxpbmUgZnVsbCBzaG9ydCBkYXRlIHN1YmplY3QNCiAgICB3aGlsZSBJRlM9J3wnIHJlYWQgLXIgZnVsbCBzaG9ydCBkYXRlIHN1YmplY3Q7IGRvDQogICAgICAgIFsgLXogIiRmdWxsIiBdICYmIGNvbnRpbnVlDQogICAgICAgIENPTU1JVF9GVUxMKz0oIiRmdWxsIikNCiAgICAgICAgQ09NTUlUX1NIT1JUKz0oIiRzaG9ydCIpDQogICAgICAgIENPTU1JVF9EQVRFKz0oIiRkYXRlIikNCiAgICAgICAgQ09NTUlUX1NVQkpFQ1QrPSgiJHN1YmplY3QiKQ0KICAgIGRvbmUgPCA8KGdpdCAtQyAiJFRIRU1FX1BBVEgiIGxvZyAtLWFsbCAtLXByZXR0eT1mb3JtYXQ6IiVIfCVofCVhZHwlcyIgLS1kYXRlPXNob3J0KQ0KfQ0KDQpzaG93X2NvbW1pdF9oaXN0b3J5KCkgew0KICAgIGlmIFsgISAtZCAiJFRIRU1FX1BBVEgiIF07IHRoZW4NCiAgICAgICAgc2hvd19oZWFkZXINCiAgICAgICAgcHJpbnRmICclcyAgJXMgaXMgbm90IGluc3RhbGxlZC4lc1xuJyAiJFJFRCIgIiRUSEVNRV9OQU1FIiAiJFJFU0VUIg0KICAgICAgICBwYXVzZV9yZXR1cm4NCiAgICAgICAgcmV0dXJuDQogICAgZmkNCg0KICAgIGlmICEgdGVzdF9kZXBlbmRlbmNpZXM7IHRoZW4NCiAgICAgICAgcGF1c2VfcmV0dXJuDQogICAgICAgIHJldHVybg0KICAgIGZpDQoNCiAgICBsb2NhbCB2aWV3aW5nPTENCiAgICB3aGlsZSBbICIkdmlld2luZyIgLWVxIDEgXTsgZG8NCiAgICAgICAgZ2V0X2NvbW1pdF9saXN0DQogICAgICAgIGlmIFsgJHsjQ09NTUlUX0ZVTExbQF19IC1lcSAwIF07IHRoZW4NCiAgICAgICAgICAgIHNob3dfaGVhZGVyDQogICAgICAgICAgICBwcmludGYgJyVzICBObyBjb21taXRzIGZvdW5kLiVzXG4nICIkUkVEIiAiJFJFU0VUIg0KICAgICAgICAgICAgcGF1c2VfcmV0dXJuDQogICAgICAgICAgICByZXR1cm4NCiAgICAgICAgZmkNCg0KICAgICAgICBsb2NhbCBjdXJyZW50X2hhc2gNCiAgICAgICAgY3VycmVudF9oYXNoPSQoZ2l0IC1DICIkVEhFTUVfUEFUSCIgcmV2LXBhcnNlIEhFQUQgMj4vZGV2L251bGwpDQoNCiAgICAgICAgbG9jYWwgY3VycmVudF9pbmRleD0tMQ0KICAgICAgICBsb2NhbCBpdGVtcz0oKQ0KICAgICAgICBsb2NhbCBpDQogICAgICAgIGZvciAoKGkgPSAwOyBpIDwgJHsjQ09NTUlUX0ZVTExbQF19OyBpKyspKTsgZG8NCiAgICAgICAgICAgIGl0ZW1zKz0oIiR7Q09NTUlUX1NIT1JUWyRpXX0gICR7Q09NTUlUX0RBVEVbJGldfSAgJHtDT01NSVRfU1VCSkVDVFskaV19IikNCiAgICAgICAgICAgIGlmIFsgIiR7Q09NTUlUX0ZVTExbJGldfSIgPSAiJGN1cnJlbnRfaGFzaCIgXTsgdGhlbg0KICAgICAgICAgICAgICAgIGN1cnJlbnRfaW5kZXg9JGkNCiAgICAgICAgICAgIGZpDQogICAgICAgIGRvbmUNCiAgICAgICAgbG9jYWwgcmV0dXJuX2xhdGVzdF9pbmRleD0keyNpdGVtc1tAXX0NCiAgICAgICAgaXRlbXMrPSgiUmV0dXJuIHRvIGxhdGVzdCB2ZXJzaW9uIikNCiAgICAgICAgbG9jYWwgYmFja19pbmRleD0keyNpdGVtc1tAXX0NCiAgICAgICAgaXRlbXMrPSgiQmFjayIpDQoNCiAgICAgICAgbG9jYWwgdGl0bGVfbGluZXM9KCIke09SQU5HRV9MSUdIVH0gIENvbW1pdCBIaXN0b3J5JHtSRVNFVH0iICIiKQ0KDQogICAgICAgIGxvY2FsIHNlbGVjdGlvbg0KICAgICAgICByZWFkX2Fycm93X3NlbGVjdGlvbiBzZWxlY3Rpb24gaXRlbXMgIiRjdXJyZW50X2luZGV4IiB0aXRsZV9saW5lcw0KDQogICAgICAgIGlmIFsgIiRzZWxlY3Rpb24iIC1lcSAtMSBdIHx8IFsgIiRzZWxlY3Rpb24iIC1lcSAiJGJhY2tfaW5kZXgiIF07IHRoZW4NCiAgICAgICAgICAgIHZpZXdpbmc9MA0KICAgICAgICBlbGlmIFsgIiRzZWxlY3Rpb24iIC1lcSAiJHJldHVybl9sYXRlc3RfaW5kZXgiIF07IHRoZW4NCiAgICAgICAgICAgIHVwZGF0ZV90aGVtZQ0KICAgICAgICBlbGlmIFsgIiRzZWxlY3Rpb24iIC1nZSAwIF0gJiYgWyAiJHNlbGVjdGlvbiIgLWx0ICR7I0NPTU1JVF9GVUxMW0BdfSBdOyB0aGVuDQogICAgICAgICAgICBjaGVja291dF9jb21taXQgIiR7Q09NTUlUX0ZVTExbJHNlbGVjdGlvbl19IiAiJHtDT01NSVRfU0hPUlRbJHNlbGVjdGlvbl19IiAiJHtDT01NSVRfREFURVskc2VsZWN0aW9uXX0iICIke0NPTU1JVF9TVUJKRUNUWyRzZWxlY3Rpb25dfSINCiAgICAgICAgZmkNCiAgICBkb25lDQp9DQoNCmNoZWNrb3V0X2NvbW1pdCgpIHsNCiAgICBsb2NhbCBmdWxsPSIkMSIgc2hvcnQ9IiQyIiBkYXRlPSIkMyIgc3ViamVjdD0iJDQiDQogICAgc2hvd19oZWFkZXINCiAgICBwcmludGYgJyVzICBDaGVja2luZyBvdXQgY29tbWl0ICVzLi4uJXNcbicgIiRPUkFOR0VfTElHSFQiICIkc2hvcnQiICIkUkVTRVQiDQogICAgcHJpbnRmICclcyAgJXMgICVzJXNcbicgIiRHUkFZIiAiJGRhdGUiICIkc3ViamVjdCIgIiRSRVNFVCINCiAgICBlY2hvICIiDQogICAgcHJpbnRmICclcyAgVGhpcyB3aWxsIHN3aXRjaCB0aGUgdGhlbWUgdG8gdGhpcyBzcGVjaWZpYyB2ZXJzaW9uLiVzXG4nICIkR1JBWSIgIiRSRVNFVCINCiAgICByZWFkIC1ycCAiICBUeXBlIFkgdG8gY29uZmlybTogIiBjb25maXJtDQogICAgaWYgWyAiJGNvbmZpcm0iICE9ICJZIiBdICYmIFsgIiRjb25maXJtIiAhPSAieSIgXTsgdGhlbg0KICAgICAgICBwcmludGYgJyVzICBDYW5jZWxsZWQuJXNcbicgIiRHUkFZIiAiJFJFU0VUIg0KICAgICAgICBwYXVzZV9yZXR1cm4NCiAgICAgICAgcmV0dXJuDQogICAgZmkNCg0KICAgIGdpdCAtQyAiJFRIRU1FX1BBVEgiIGNoZWNrb3V0ICIkZnVsbCIgLS1xdWlldA0KDQogICAgaWYgY29tbWFuZCAtdiBzcGljZXRpZnkgPi9kZXYvbnVsbCAyPiYxOyB0aGVuDQogICAgICAgIGVjaG8gIiINCiAgICAgICAgcHJpbnRmICclcyAgQXBwbHlpbmcgU3BpY2V0aWZ5Li4uJXNcbicgIiRPUkFOR0VfTUlEIiAiJFJFU0VUIg0KICAgICAgICBzcGljZXRpZnkgYXBwbHkNCiAgICBmaQ0KDQogICAgZWNobyAiIg0KICAgIHByaW50ZiAnJXMgICVzIGlzIG5vdyBvbiBjb21taXQgJXMuJXNcbicgIiRHUkVFTiIgIiRUSEVNRV9OQU1FIiAiJHNob3J0IiAiJFJFU0VUIg0KICAgIHBhdXNlX3JldHVybg0KfQ0KDQpjaGVja19mb3JfdXBkYXRlcygpIHsNCiAgICBzaG93X2hlYWRlcg0KICAgIHByaW50ZiAnJXMgIENoZWNraW5nIGZvciB1cGRhdGVzLi4uJXNcbicgIiRPUkFOR0VfTElHSFQiICIkUkVTRVQiDQogICAgZWNobyAiIg0KDQogICAgaWYgWyAhIC1kICIkVEhFTUVfUEFUSCIgXTsgdGhlbg0KICAgICAgICBwcmludGYgJyVzICAlcyBpcyBub3QgaW5zdGFsbGVkLiVzXG4nICIkUkVEIiAiJFRIRU1FX05BTUUiICIkUkVTRVQiDQogICAgICAgIHBhdXNlX3JldHVybg0KICAgICAgICByZXR1cm4NCiAgICBmaQ0KDQogICAgaWYgISB0ZXN0X2RlcGVuZGVuY2llczsgdGhlbg0KICAgICAgICBwYXVzZV9yZXR1cm4NCiAgICAgICAgcmV0dXJuDQogICAgZmkNCg0KICAgIGdldF90aGVtZV9zdGF0dXNfZGV0YWlsZWQNCiAgICBwcmludGYgJyVzICBTdGF0dXM6ICVzJXMlc1xuJyAiJFdISVRFIiAiJGdldF90aGVtZV9zdGF0dXNfY29sb3IiICIkZ2V0X3RoZW1lX3N0YXR1c190ZXh0IiAiJFJFU0VUIg0KDQogICAgaWYgWyAiJGdldF90aGVtZV9zdGF0dXNfdGV4dCIgPSAiSW5zdGFsbGVkIChvdXRkYXRlZCkiIF07IHRoZW4NCiAgICAgICAgZWNobyAiIg0KICAgICAgICBwcmludGYgJyVzICBBIG5ld2VyIHZlcnNpb24gaXMgYXZhaWxhYmxlLiVzXG4nICIkR1JBWSIgIiRSRVNFVCINCiAgICAgICAgcmVhZCAtcnAgIiAgVHlwZSBZIHRvIHVwZGF0ZSBub3c6ICIgY29uZmlybQ0KICAgICAgICBpZiBbICIkY29uZmlybSIgPSAiWSIgXSB8fCBbICIkY29uZmlybSIgPSAieSIgXTsgdGhlbg0KICAgICAgICAgICAgdXBkYXRlX3RoZW1lDQogICAgICAgICAgICByZXR1cm4NCiAgICAgICAgZmkNCiAgICBmaQ0KDQogICAgcGF1c2VfcmV0dXJuDQp9DQoNCnBhdXNlX3JldHVybigpIHsNCiAgICBlY2hvICIiDQogICAgcHJpbnRmICclcyAgUHJlc3MgYW55IGtleSB0byByZXR1cm4gdG8gdGhlIG1lbnUuLi4lc1xuJyAiJEdSQVkiICIkUkVTRVQiDQogICAgSUZTPSByZWFkIC1yc24xDQp9DQoNCnNob3dfbWVudSgpIHsNCiAgICBzaG93X2hlYWRlcg0KICAgIGdldF90aGVtZV9zdGF0dXNfZGV0YWlsZWQNCg0KICAgIHByaW50ZiAnJXMgIFN0YXR1czogJXMlcyVzXG4nICIkV0hJVEUiICIkZ2V0X3RoZW1lX3N0YXR1c19jb2xvciIgIiRnZXRfdGhlbWVfc3RhdHVzX3RleHQiICIkUkVTRVQiDQogICAgZWNobyAiIg0KICAgIHByaW50ZiAnJXMgIFsxXSBJbnN0YWxsICVzJXNcbicgIiRXSElURSIgIiRUSEVNRV9OQU1FIiAiJFJFU0VUIg0KICAgIHByaW50ZiAnJXMgIFsyXSBVcGRhdGUgJXMlc1xuJyAiJFdISVRFIiAiJFRIRU1FX05BTUUiICIkUkVTRVQiDQogICAgcHJpbnRmICclcyAgWzNdIFVuaW5zdGFsbCAlcyVzXG4nICIkV0hJVEUiICIkVEhFTUVfTkFNRSIgIiRSRVNFVCINCiAgICBwcmludGYgJyVzICBbNF0gQ29tbWl0IEhpc3RvcnkgLyBEb3duZ3JhZGUlc1xuJyAiJFdISVRFIiAiJFJFU0VUIg0KICAgIHByaW50ZiAnJXMgIFs1XSBDaGVjayBmb3IgVXBkYXRlcyVzXG4nICIkV0hJVEUiICIkUkVTRVQiDQogICAgcHJpbnRmICclcyAgWzZdIEV4aXQlc1xuJyAiJFdISVRFIiAiJFJFU0VUIg0KICAgIGVjaG8gIiINCiAgICBwcmludGYgJyVzICA9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09JXNcbicgIiRPUkFOR0VfREFSSyIgIiRSRVNFVCINCiAgICBlY2hvICIiDQogICAgcmVhZCAtcnAgIiAgU2VsZWN0IGFuIG9wdGlvbjogIiBjaG9pY2UNCiAgICBNRU5VX0NIT0lDRT0iJGNob2ljZSINCn0NCg0KcnVubmluZz0xDQp3aGlsZSBbICIkcnVubmluZyIgLWVxIDEgXTsgZG8NCiAgICBzaG93X21lbnUNCiAgICBjaG9pY2U9IiRNRU5VX0NIT0lDRSINCiAgICBjYXNlICIkY2hvaWNlIiBpbg0KICAgICAgICAxKSBpbnN0YWxsX3RoZW1lIDs7DQogICAgICAgIDIpIHVwZGF0ZV90aGVtZSA7Ow0KICAgICAgICAzKSB1bmluc3RhbGxfdGhlbWUgOzsNCiAgICAgICAgNCkgc2hvd19jb21taXRfaGlzdG9yeSA7Ow0KICAgICAgICA1KSBjaGVja19mb3JfdXBkYXRlcyA7Ow0KICAgICAgICA2KSBydW5uaW5nPTAgOzsNCiAgICAgICAgKikNCiAgICAgICAgICAgIHNob3dfaGVhZGVyDQogICAgICAgICAgICBwcmludGYgJyVzICBJbnZhbGlkIG9wdGlvbi4lc1xuJyAiJFJFRCIgIiRSRVNFVCINCiAgICAgICAgICAgIHBhdXNlX3JldHVybg0KICAgICAgICAgICAgOzsNCiAgICBlc2FjDQpkb25lDQoNCmNsZWFy" + +ok=true + +step1() { + mkdir -p "$targetDir" +} +invoke_step "Creating install directory" step1 || ok=false + +step2() { + echo "$spotuiScriptContentBase64" | base64 -d | tr -d '\r' > "$scriptPath" + chmod +x "$scriptPath" +} +invoke_step "Writing SpoTUI script" step2 || ok=false + +step3() { + printf '#!/usr/bin/env bash\nexec "%s" "$@"\n' "$scriptPath" > "$wrapperPath" + chmod +x "$wrapperPath" +} +invoke_step "Creating wrapper script" step3 || ok=false + +step4() { + mkdir -p "$binDir" + local profileFile="$HOME/.bashrc" + if [ -n "$ZSH_VERSION" ] || [ -f "$HOME/.zshrc" ]; then + profileFile="$HOME/.zshrc" + fi + if ! grep -q "$binDir" "$profileFile" 2>/dev/null; then + echo "export PATH=\"\$PATH:$binDir\"" >> "$profileFile" fi } +invoke_step "Updating PATH" step4 || ok=false -get_theme_status_text="" -get_theme_status_color="" +export PATH="$PATH:$binDir" -get_theme_status_detailed() { - if [ ! -d "$THEME_PATH" ]; then - get_theme_status_text="Not Installed" - get_theme_status_color="$RED" - return - fi - - if ! command -v git >/dev/null 2>&1; then - get_theme_status_text="Installed" - get_theme_status_color="$GREEN" - return - fi - - git -C "$THEME_PATH" fetch origin --quiet 2>/dev/null - local local_hash - local_hash=$(git -C "$THEME_PATH" rev-parse HEAD 2>/dev/null) - - if test_is_detached; then - local short_hash="unknown" - [ -n "$local_hash" ] && short_hash="${local_hash:0:7}" - get_theme_status_text="Installed (custom commit $short_hash)" - get_theme_status_color="$CYAN" - return - fi - - local branch remote_hash - branch=$(get_default_branch) - remote_hash=$(git -C "$THEME_PATH" rev-parse "origin/$branch" 2>/dev/null) - - if [ -z "$local_hash" ] || [ -z "$remote_hash" ]; then - get_theme_status_text="Installed" - get_theme_status_color="$GREEN" - return - fi - - if [ "$local_hash" = "$remote_hash" ]; then - get_theme_status_text="Installed (up to date)" - get_theme_status_color="$GREEN" - else - get_theme_status_text="Installed (outdated)" - get_theme_status_color="$RED" - fi +step5() { + printf '#!/usr/bin/env bash\nexec "%s" "$@"\n' "$wrapperPath" > "$binPath" + chmod +x "$binPath" } +invoke_step "Creating launcher" step5 || ok=false -install_theme() { - show_header - printf '%s Installing %s...%s\n' "$ORANGE_LIGHT" "$THEME_NAME" "$RESET" - echo "" +echo "" +echo "${OrangeDark} =============================================================${Reset}" +echo "" - if ! test_dependencies; then - pause_return - return - fi +if $ok; then + echo " ${GreenAnsi}${CheckMark} SpoTUI installed successfully.${Reset}" + echo " ${GrayAnsi} Open a new terminal and run: spotui${Reset}" +else + echo " ${RedAnsi}${CrossMark} Installation finished with errors. See above.${Reset}" +fi - mkdir -p "$THEMES_DIR" - - if [ -d "$THEME_PATH" ]; then - printf '%s Theme already exists locally. Pulling latest changes...%s\n' "$ORANGE_MID" "$RESET" - git -C "$THEME_PATH" pull - else - (cd "$THEMES_DIR" && git clone "$REPO_URL" "$THEME_NAME") - fi - - if [ -d "$THEME_PATH" ]; then - echo "" - printf '%s Setting current theme to %s...%s\n' "$ORANGE_MID" "$THEME_NAME" "$RESET" - spicetify config current_theme "$THEME_NAME" - - printf '%s Applying Spicetify...%s\n' "$ORANGE_MID" "$RESET" - spicetify apply - - echo "" - printf '%s %s installed and applied successfully.%s\n' "$GREEN" "$THEME_NAME" "$RESET" - else - echo "" - printf '%s Installation failed. Check the errors above.%s\n' "$RED" "$RESET" - fi - - pause_return -} - -update_theme() { - show_header - printf '%s Updating %s...%s\n' "$ORANGE_LIGHT" "$THEME_NAME" "$RESET" - echo "" - - if [ ! -d "$THEME_PATH" ]; then - printf '%s %s is not installed. Use Install instead.%s\n' "$RED" "$THEME_NAME" "$RESET" - pause_return - return - fi - - if ! test_dependencies; then - pause_return - return - fi - - if test_is_detached; then - local branch - branch=$(get_default_branch) - printf '%s Currently on a custom commit. Returning to %s...%s\n' "$ORANGE_MID" "$branch" "$RESET" - git -C "$THEME_PATH" checkout "$branch" --quiet - fi - - git -C "$THEME_PATH" pull - - echo "" - printf '%s Re-applying Spicetify...%s\n' "$ORANGE_MID" "$RESET" - spicetify apply - - echo "" - printf '%s %s updated successfully.%s\n' "$GREEN" "$THEME_NAME" "$RESET" - pause_return -} - -uninstall_theme() { - show_header - printf '%s Uninstalling %s...%s\n' "$ORANGE_LIGHT" "$THEME_NAME" "$RESET" - echo "" - - if [ ! -d "$THEME_PATH" ]; then - printf '%s %s is not installed.%s\n' "$RED" "$THEME_NAME" "$RESET" - pause_return - return - fi - - printf '%s This will remove the theme folder and switch to Marketplace.%s\n' "$GRAY" "$RESET" - read -rp " Type Y to confirm: " confirm - if [ "$confirm" != "Y" ] && [ "$confirm" != "y" ]; then - printf '%s Cancelled.%s\n' "$GRAY" "$RESET" - pause_return - return - fi - - if command -v spicetify >/dev/null 2>&1; then - printf '%s Switching Spicetify theme...%s\n' "$ORANGE_MID" "$RESET" - spicetify config current_theme "SpoTUI-" - spicetify config current_theme marketplace - spicetify apply - fi - - rm -rf "$THEME_PATH" - - echo "" - printf '%s %s has been uninstalled.%s\n' "$GREEN" "$THEME_NAME" "$RESET" - pause_return -} - -read_key() { - local key rest - IFS= read -rsn1 key - if [ "$key" = "$ESC" ]; then - read -rsn2 -t 0.01 rest - case "$rest" in - "[A") echo "UP" ;; - "[B") echo "DOWN" ;; - *) echo "ESC" ;; - esac - elif [ -z "$key" ]; then - echo "ENTER" - else - echo "OTHER" - fi -} - -read_arrow_selection() { - local -n out_result=$1 - local -n _ras_items=$2 - local current_index=$3 - local -n _ras_title_lines=$4 - - local selected_index=0 - [ "$current_index" -ge 0 ] && selected_index=$current_index - - get_header_lines - local overhead=$(( ${#HEADER_LINES[@]} + ${#_ras_title_lines[@]} + 4 )) - - local page_size - page_size=$(( $(get_console_height) - overhead )) - [ "$page_size" -gt "${#_ras_items[@]}" ] && page_size=${#_ras_items[@]} - [ "$page_size" -lt 1 ] && page_size=1 - - clear - printf '%s[?25l' "$ESC" - - while true; do - local total_pages=$(( (${#_ras_items[@]} + page_size - 1) / page_size )) - [ "$total_pages" -lt 1 ] && total_pages=1 - local current_page=$(( selected_index / page_size )) - local page_start=$(( current_page * page_size )) - local page_end=$(( page_start + page_size - 1 )) - [ "$page_end" -gt $((${#_ras_items[@]} - 1)) ] && page_end=$((${#_ras_items[@]} - 1)) - - local frame=() - frame+=("${HEADER_LINES[@]}") - frame+=("${_ras_title_lines[@]}") - - local i prefix text - for ((i = page_start; i <= page_end; i++)); do - if [ "$i" -eq "$current_index" ]; then - prefix="> " - else - prefix=" " - fi - text="${prefix}${_ras_items[$i]}" - if [ "$i" -eq "$selected_index" ]; then - frame+=("${SELECT_BG}${text}${RESET}") - elif [ "$i" -eq "$current_index" ]; then - frame+=("${GREEN_ANSI}${text}${RESET}") - else - frame+=("$text") - fi - done - - local lines_used=$(( page_end - page_start + 1 )) - local p - for ((p = lines_used; p < page_size; p++)); do - frame+=("") - done - - frame+=("") - frame+=("${ORANGE_DARK} =============================================================${RESET}") - frame+=(" Up/Down to move, Enter to select, Esc to go back Page $((current_page + 1)) of $total_pages") - - write_frame frame - - local key - key=$(read_key) - - case "$key" in - UP) - if [ "$selected_index" -gt 0 ]; then - selected_index=$((selected_index - 1)) - else - selected_index=$((${#_ras_items[@]} - 1)) - fi - ;; - DOWN) - if [ "$selected_index" -lt $((${#_ras_items[@]} - 1)) ]; then - selected_index=$((selected_index + 1)) - else - selected_index=0 - fi - ;; - ENTER) - printf '%s[?25h' "$ESC" - out_result=$selected_index - return - ;; - ESC) - printf '%s[?25h' "$ESC" - out_result=-1 - return - ;; - esac - done -} - -get_commit_list() { - git -C "$THEME_PATH" fetch origin --quiet 2>/dev/null - COMMIT_FULL=() - COMMIT_SHORT=() - COMMIT_DATE=() - COMMIT_SUBJECT=() - local line full short date subject - while IFS='|' read -r full short date subject; do - [ -z "$full" ] && continue - COMMIT_FULL+=("$full") - COMMIT_SHORT+=("$short") - COMMIT_DATE+=("$date") - COMMIT_SUBJECT+=("$subject") - done < <(git -C "$THEME_PATH" log --all --pretty=format:"%H|%h|%ad|%s" --date=short) -} - -show_commit_history() { - if [ ! -d "$THEME_PATH" ]; then - show_header - printf '%s %s is not installed.%s\n' "$RED" "$THEME_NAME" "$RESET" - pause_return - return - fi - - if ! test_dependencies; then - pause_return - return - fi - - local viewing=1 - while [ "$viewing" -eq 1 ]; do - get_commit_list - if [ ${#COMMIT_FULL[@]} -eq 0 ]; then - show_header - printf '%s No commits found.%s\n' "$RED" "$RESET" - pause_return - return - fi - - local current_hash - current_hash=$(git -C "$THEME_PATH" rev-parse HEAD 2>/dev/null) - - local current_index=-1 - local items=() - local i - for ((i = 0; i < ${#COMMIT_FULL[@]}; i++)); do - items+=("${COMMIT_SHORT[$i]} ${COMMIT_DATE[$i]} ${COMMIT_SUBJECT[$i]}") - if [ "${COMMIT_FULL[$i]}" = "$current_hash" ]; then - current_index=$i - fi - done - local return_latest_index=${#items[@]} - items+=("Return to latest version") - local back_index=${#items[@]} - items+=("Back") - - local title_lines=("${ORANGE_LIGHT} Commit History${RESET}" "") - - local selection - read_arrow_selection selection items "$current_index" title_lines - - if [ "$selection" -eq -1 ] || [ "$selection" -eq "$back_index" ]; then - viewing=0 - elif [ "$selection" -eq "$return_latest_index" ]; then - update_theme - elif [ "$selection" -ge 0 ] && [ "$selection" -lt ${#COMMIT_FULL[@]} ]; then - checkout_commit "${COMMIT_FULL[$selection]}" "${COMMIT_SHORT[$selection]}" "${COMMIT_DATE[$selection]}" "${COMMIT_SUBJECT[$selection]}" - fi - done -} - -checkout_commit() { - local full="$1" short="$2" date="$3" subject="$4" - show_header - printf '%s Checking out commit %s...%s\n' "$ORANGE_LIGHT" "$short" "$RESET" - printf '%s %s %s%s\n' "$GRAY" "$date" "$subject" "$RESET" - echo "" - printf '%s This will switch the theme to this specific version.%s\n' "$GRAY" "$RESET" - read -rp " Type Y to confirm: " confirm - if [ "$confirm" != "Y" ] && [ "$confirm" != "y" ]; then - printf '%s Cancelled.%s\n' "$GRAY" "$RESET" - pause_return - return - fi - - git -C "$THEME_PATH" checkout "$full" --quiet - - if command -v spicetify >/dev/null 2>&1; then - echo "" - printf '%s Applying Spicetify...%s\n' "$ORANGE_MID" "$RESET" - spicetify apply - fi - - echo "" - printf '%s %s is now on commit %s.%s\n' "$GREEN" "$THEME_NAME" "$short" "$RESET" - pause_return -} - -check_for_updates() { - show_header - printf '%s Checking for updates...%s\n' "$ORANGE_LIGHT" "$RESET" - echo "" - - if [ ! -d "$THEME_PATH" ]; then - printf '%s %s is not installed.%s\n' "$RED" "$THEME_NAME" "$RESET" - pause_return - return - fi - - if ! test_dependencies; then - pause_return - return - fi - - get_theme_status_detailed - printf '%s Status: %s%s%s\n' "$WHITE" "$get_theme_status_color" "$get_theme_status_text" "$RESET" - - if [ "$get_theme_status_text" = "Installed (outdated)" ]; then - echo "" - printf '%s A newer version is available.%s\n' "$GRAY" "$RESET" - read -rp " Type Y to update now: " confirm - if [ "$confirm" = "Y" ] || [ "$confirm" = "y" ]; then - update_theme - return - fi - fi - - pause_return -} - -pause_return() { - echo "" - printf '%s Press any key to return to the menu...%s\n' "$GRAY" "$RESET" - IFS= read -rsn1 -} - -show_menu() { - show_header - get_theme_status_detailed - - printf '%s Status: %s%s%s\n' "$WHITE" "$get_theme_status_color" "$get_theme_status_text" "$RESET" - echo "" - printf '%s [1] Install %s%s\n' "$WHITE" "$THEME_NAME" "$RESET" - printf '%s [2] Update %s%s\n' "$WHITE" "$THEME_NAME" "$RESET" - printf '%s [3] Uninstall %s%s\n' "$WHITE" "$THEME_NAME" "$RESET" - printf '%s [4] Commit History / Downgrade%s\n' "$WHITE" "$RESET" - printf '%s [5] Check for Updates%s\n' "$WHITE" "$RESET" - printf '%s [6] Exit%s\n' "$WHITE" "$RESET" - echo "" - printf '%s =============================================================%s\n' "$ORANGE_DARK" "$RESET" - echo "" - read -rp " Select an option: " choice - MENU_CHOICE="$choice" -} - -running=1 -while [ "$running" -eq 1 ]; do - show_menu - choice="$MENU_CHOICE" - case "$choice" in - 1) install_theme ;; - 2) update_theme ;; - 3) uninstall_theme ;; - 4) show_commit_history ;; - 5) check_for_updates ;; - 6) running=0 ;; - *) - show_header - printf '%s Invalid option.%s\n' "$RED" "$RESET" - pause_return - ;; - esac -done - -clear \ No newline at end of file +echo "" \ No newline at end of file diff --git a/scripts/uninstall/linux/uninstall.sh b/scripts/uninstall/linux/uninstall.sh new file mode 100644 index 0000000..85ef396 --- /dev/null +++ b/scripts/uninstall/linux/uninstall.sh @@ -0,0 +1,165 @@ +#!/usr/bin/env bash +grep -q $'\r' "$0" 2>/dev/null && tr -d '\r' < "$0" > "$0.lf" && chmod +x "$0.lf" && exec bash "$0.lf" "$@" + +Esc=$'\033' +Reset="${Esc}[0m" + +BlockFull=$'\u2588' +BlockLower=$'\u2584' +BlockUpper=$'\u2580' +BlockLeft=$'\u258C' + +get_rgb() { + local r=$1 g=$2 b=$3 + echo "${Esc}[38;2;${r};${g};${b}m" +} + +OrangeLight=$(get_rgb 255 140 66) +OrangeDark=$(get_rgb 224 123 57) +OrangeMid=$(get_rgb 240 131 61) +GreenAnsi=$(get_rgb 140 255 140) +RedAnsi=$(get_rgb 255 110 110) +GrayAnsi=$(get_rgb 130 130 130) + +get_gradient_color() { + local index=$1 total=$2 + local r1=255 g1=140 b1=66 + local r2=224 g2=123 b2=57 + local t=0 + if [ "$total" -gt 1 ]; then + t=$(awk -v i="$index" -v t="$total" 'BEGIN { printf "%.6f", i/(t-1) }') + fi + local r g b + r=$(awk -v r1="$r1" -v r2="$r2" -v t="$t" 'BEGIN { printf "%d", r1+(r2-r1)*t }') + g=$(awk -v g1="$g1" -v g2="$g2" -v t="$t" 'BEGIN { printf "%d", g1+(g2-g1)*t }') + b=$(awk -v b1="$b1" -v b2="$b2" -v t="$t" 'BEGIN { printf "%d", b1+(b2-b1)*t }') + get_rgb "$r" "$g" "$b" +} + +get_ascii_art_line() { + local out="$1" + out="${out//A/$BlockFull}" + out="${out//B/$BlockLower}" + out="${out//C/$BlockUpper}" + out="${out//D/$BlockLeft}" + echo "$out" +} + +show_header() { + clear + local templates=( + " BAAAAAAAA BAAAAAAAB BAAAAAAAB AAA AAA AB BA " + " AAA AAA AAA AAA AAA AAA CAAAAAAAAAB AAA AAA AAA " + " AAA AC AAA AAA AAA AAA CAAACCAA AAA AAA AAAD " + " AAA AAA AAA AAA AAA AAA C AAA AAA AAAD " + "CAAAAAAAAAAA CAAAAAAAAAC AAA AAA AAA AAA AAA AAAD " + " AAA AAA AAA AAA AAA AAA AAA AAA " + " BA AAA AAA AAA AAA AAA AAA AAA AAA " + " BAAAAAAAAC BAAAAC CAAAAAAC BAAAAC AAAAAAAAC AC " + ) + echo "" + local total=${#templates[@]} + local i=0 + for tmpl in "${templates[@]}"; do + local color + color=$(get_gradient_color "$i" "$total") + local line + line=$(get_ascii_art_line "$tmpl") + echo "${color}${line}${Reset}" + i=$((i + 1)) + done + echo "" + echo "${OrangeMid} Uninstaller${Reset}" + echo "${OrangeDark} =============================================================${Reset}" + echo "" +} + +SpinnerFrames=($'\u280B' $'\u2819' $'\u2839' $'\u2838' $'\u283C' $'\u2834' $'\u2826' $'\u2827' $'\u2807' $'\u280F') +CheckMark=$'\u2713' +CrossMark=$'\u2715' +CR=$'\r' + +invoke_step() { + local label="$1" + local workfn="$2" + + tput civis 2>/dev/null + + local tmpfile + tmpfile=$(mktemp) + "$workfn" >"$tmpfile" 2>&1 & + local pid=$! + + local frameIndex=0 + while kill -0 "$pid" 2>/dev/null; do + local frame="${SpinnerFrames[$((frameIndex % ${#SpinnerFrames[@]}))]}" + local color + color=$(get_gradient_color $((frameIndex % 8)) 8) + printf "%s %s%s%s %s" "$CR" "$color" "$frame" "$Reset" "$label" + sleep 0.08 + frameIndex=$((frameIndex + 1)) + done + + wait "$pid" + local status=$? + rm -f "$tmpfile" + + local pad + pad=$(printf ' %.0s' {1..40}) + if [ "$status" -ne 0 ]; then + printf "%s %s%s%s %s%s\n" "$CR" "$RedAnsi" "$CrossMark" "$Reset" "$label" "$pad" + else + printf "%s %s%s%s %s%s\n" "$CR" "$GreenAnsi" "$CheckMark" "$Reset" "$label" "$pad" + fi + + tput cnorm 2>/dev/null + return $status +} + +show_header + +targetDirs=("$HOME/.local/share/spotui" "$HOME/.local/share/spotui-cli") +binPath="$HOME/.local/bin/spotui" + +ok=true + +for dir in "${targetDirs[@]}"; do + remove_dir() { + if [ -d "$dir" ]; then + rm -rf "$dir" + fi + } + invoke_step "Removing $dir" remove_dir || ok=false +done + +remove_bin() { + if [ -f "$binPath" ]; then + rm -f "$binPath" + fi +} +invoke_step "Removing $binPath" remove_bin || ok=false + +clean_path() { + local profileFile + for profileFile in "$HOME/.bashrc" "$HOME/.zshrc"; do + if [ -f "$profileFile" ]; then + for dir in "${targetDirs[@]}" "$HOME/.local/bin"; do + sed -i "\#export PATH=\"\$PATH:$dir\"#d" "$profileFile" + done + fi + done +} +invoke_step "Cleaning PATH" clean_path || ok=false + +echo "" +echo "${OrangeDark} =============================================================${Reset}" +echo "" + +if $ok; then + echo " ${GreenAnsi}${CheckMark} SpoTUI has been uninstalled.${Reset}" + echo " ${GrayAnsi} Restart your terminal for PATH changes to take full effect.${Reset}" +else + echo " ${RedAnsi}${CrossMark} Uninstall finished with errors. See above.${Reset}" +fi + +echo "" \ No newline at end of file