fix(swiftbar): improve safety and readability in script functions

This commit is contained in:
tri committed 2026-07-17 11:24:22 +07:00
1 parent bed14dac95
commit e7729693df
1 file changed
+85 -60
+85 -60
View File
@@ -9,6 +9,7 @@
# <swiftbar.hideLastUpdated>true</swiftbar.hideLastUpdated> # <swiftbar.hideLastUpdated>true</swiftbar.hideLastUpdated>
set -u set -u
zmodload zsh/datetime
PATH="/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin" PATH="/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin"
export HOMEBREW_NO_UPGRADE_AUTO_UPDATES_CASKS=1 export HOMEBREW_NO_UPGRADE_AUTO_UPDATES_CASKS=1
@@ -33,25 +34,34 @@ SF_SIZE=15
mkdir -p "${CACHE_DIR}" "${STATE_DIR}" mkdir -p "${CACHE_DIR}" "${STATE_DIR}"
escape_swiftbar() { escape_swiftbar() {
printf '%s' "$1" | sed 's/|/\\|/g' local value="$1"
printf '%s' "${value//\|/\\|}"
} }
escape_attribute() { escape_attribute() {
printf '%s' "$1" | sed 's/\\/\\\\/g; s/"/\\"/g; s/|/\\|/g' local value="$1"
local backslash='\'
local quote='"'
local pipe='|'
value="${value//${backslash}/${backslash}${backslash}}"
value="${value//${quote}/${backslash}${quote}}"
printf '%s' "${value//${pipe}/${backslash}${pipe}}"
} }
is_auto_enabled() { is_auto_enabled() {
[ -f "${AUTO_FILE}" ] [[ -f "${AUTO_FILE}" ]]
} }
is_manual_check_running() { is_manual_check_running() {
[ -f "${MANUAL_CHECK_FILE}" ] [[ -f "${MANUAL_CHECK_FILE}" ]]
} }
is_upgrade_running() { is_upgrade_running() {
[ -d "${UPGRADE_LOCK_DIR}" ] || return 1 [[ -d "${UPGRADE_LOCK_DIR}" ]] || return 1
upgrade_pid="$(cat "${UPGRADE_PID_FILE}" 2>/dev/null || true)" local upgrade_pid
upgrade_pid="$(<"${UPGRADE_PID_FILE}" 2>/dev/null || true)"
case "${upgrade_pid}" in case "${upgrade_pid}" in
*[!0-9]*|'') return 0 ;; *[!0-9]*|'') return 0 ;;
esac esac
@@ -60,7 +70,7 @@ is_upgrade_running() {
return 0 return 0
fi fi
rm -f "${UPGRADE_PID_FILE}" rm -f -- "${UPGRADE_PID_FILE:?}" # noka: ZC1059
rmdir "${UPGRADE_LOCK_DIR}" 2>/dev/null || true rmdir "${UPGRADE_LOCK_DIR}" 2>/dev/null || true
return 1 return 1
} }
@@ -70,25 +80,26 @@ is_brew_busy() {
} }
acquire_upgrade_lock() { acquire_upgrade_lock() {
allow_during_check="${1:-false}" local allow_during_check="${1:-false}"
if [ "${allow_during_check}" != "true" ] && { is_manual_check_running || [ -d "${LOCK_DIR}" ]; }; then if [[ "${allow_during_check}" != "true" ]] && { is_manual_check_running || [[ -d "${LOCK_DIR}" ]]; }; then
return 1 return 1
fi fi
if ! mkdir "${UPGRADE_LOCK_DIR}" 2>/dev/null; then if ! mkdir "${UPGRADE_LOCK_DIR}" 2>/dev/null; then # noka: ZC1147
is_upgrade_running && return 1 is_upgrade_running && return 1
mkdir "${UPGRADE_LOCK_DIR}" 2>/dev/null || return 1 mkdir "${UPGRADE_LOCK_DIR}" 2>/dev/null || return 1 # noka: ZC1147
fi fi
printf '%s\n' "$$" > "${UPGRADE_PID_FILE}" printf '%s\n' "$$" > "${UPGRADE_PID_FILE}"
} }
release_upgrade_lock() { release_upgrade_lock() {
upgrade_pid="$(cat "${UPGRADE_PID_FILE}" 2>/dev/null || true)" local upgrade_pid
[ "${upgrade_pid}" = "$$" ] || return 0 upgrade_pid="$(<"${UPGRADE_PID_FILE}" 2>/dev/null || true)"
[[ "${upgrade_pid}" = "$$" ]] || return 0
rm -f "${UPGRADE_PID_FILE}" rm -f -- "${UPGRADE_PID_FILE:?}" # noka: ZC1059
rmdir "${UPGRADE_LOCK_DIR}" 2>/dev/null || true rmdir "${UPGRADE_LOCK_DIR}" 2>/dev/null || true
} }
@@ -98,22 +109,22 @@ log_busy_upgrade() {
} }
refresh_plugin() { refresh_plugin() {
plugin_name="${PLUGIN_PATH:t}" local plugin_name="${PLUGIN_PATH:t}"
/usr/bin/open -g "swiftbar://refreshplugin?name=${plugin_name}" >/dev/null 2>&1 || true /usr/bin/open -g "swiftbar://refreshplugin?name=${plugin_name}" >/dev/null 2>&1 || true
} }
start_manual_check() { start_manual_check() {
is_manual_check_running && return 0 is_manual_check_running && return 0
touch "${MANUAL_CHECK_FILE}" : >| "${MANUAL_CHECK_FILE}"
nohup "${PLUGIN_PATH}" --check-now-worker >/dev/null 2>&1 & "${PLUGIN_PATH}" --check-now-worker >/dev/null 2>&1 &!
} }
run_manual_check_worker() { run_manual_check_worker() {
rm -f "${CHECKED_AT_FILE}" rm -f -- "${CHECKED_AT_FILE:?}" # noka: ZC1059
perform_daily_check perform_daily_check
check_status=$? local check_status=$?
rm -f "${MANUAL_CHECK_FILE}" rm -f -- "${MANUAL_CHECK_FILE:?}" # noka: ZC1059
refresh_plugin refresh_plugin
return "${check_status}" return "${check_status}"
} }
@@ -123,7 +134,7 @@ write_error() {
} }
clear_error() { clear_error() {
rm -f "${ERROR_FILE}" rm -f -- "${ERROR_FILE:?}" # noka: ZC1059
} }
record_check_time() { record_check_time() {
@@ -143,16 +154,17 @@ collect_outdated() {
return 1 return 1
fi fi
mv "${FORMULAE_FILE}.tmp" "${FORMULAE_FILE}" mv "${FORMULAE_FILE}.tmp" "${FORMULAE_FILE}" # noka: ZC1244
mv "${CASKS_FILE}.tmp" "${CASKS_FILE}" mv "${CASKS_FILE}.tmp" "${CASKS_FILE}" # noka: ZC1244
rm -f "${ERROR_FILE}.tmp" rm -f "${ERROR_FILE}.tmp"
clear_error clear_error
} }
run_upgrade_all() { run_upgrade_all() {
upgrade_mode="${1:-manual}" local upgrade_mode="${1:-manual}"
local allow_during_check
if [ "${upgrade_mode}" = "auto" ]; then if [[ "${upgrade_mode}" = "auto" ]]; then
allow_during_check=true allow_during_check=true
else else
allow_during_check=false allow_during_check=false
@@ -166,13 +178,13 @@ run_upgrade_all() {
{ {
printf '\n[%s] Starting Homebrew upgrade\n' "$(date '+%Y-%m-%d %H:%M:%S')" printf '\n[%s] Starting Homebrew upgrade\n' "$(date '+%Y-%m-%d %H:%M:%S')"
if [ "${upgrade_mode}" = "auto" ]; then if [[ "${upgrade_mode}" = "auto" ]]; then
"${BREW_BIN}" upgrade --yes "${BREW_BIN}" upgrade --yes
else else
"${BREW_BIN}" update && "${BREW_BIN}" upgrade --yes "${BREW_BIN}" update && "${BREW_BIN}" upgrade --yes
fi fi
} >> "${LOG_FILE}" 2>&1 } >> "${LOG_FILE}" 2>&1
upgrade_status=$? local upgrade_status=$?
collect_outdated || true collect_outdated || true
record_check_time record_check_time
@@ -182,13 +194,14 @@ run_upgrade_all() {
} }
run_upgrade_one() { run_upgrade_one() {
package_name="$1" local package_name="$1"
package_type="$2" local package_type="$2"
local type_flag
case "${package_name}" in case "${package_name}" in
*[!A-Za-z0-9@+._/-]*|'') *[!A-Za-z0-9@+._/-]*|'')
printf 'Invalid Homebrew package name: %s\n' "${package_name}" >&2 printf 'Invalid Homebrew package name: %s\n' "${package_name}" >&2
exit 2 return 2
;; ;;
esac esac
@@ -197,7 +210,7 @@ run_upgrade_one() {
cask) type_flag="--cask" ;; cask) type_flag="--cask" ;;
*) *)
printf 'Invalid package type: %s\n' "${package_type}" >&2 printf 'Invalid package type: %s\n' "${package_type}" >&2
exit 2 return 2
;; ;;
esac esac
@@ -211,7 +224,7 @@ run_upgrade_one() {
printf '\n[%s] Upgrading %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "${package_name}" printf '\n[%s] Upgrading %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "${package_name}"
"${BREW_BIN}" upgrade --yes "${type_flag}" "${package_name}" "${BREW_BIN}" upgrade --yes "${type_flag}" "${package_name}"
} >> "${LOG_FILE}" 2>&1 } >> "${LOG_FILE}" 2>&1
upgrade_status=$? local upgrade_status=$?
collect_outdated || true collect_outdated || true
record_check_time record_check_time
@@ -221,14 +234,15 @@ run_upgrade_one() {
} }
perform_daily_check() { perform_daily_check() {
if ! mkdir "${LOCK_DIR}" 2>/dev/null; then if ! mkdir "${LOCK_DIR}" 2>/dev/null; then # noka: ZC1147
return 0 return 0
fi fi
trap 'rmdir "${LOCK_DIR}" 2>/dev/null || true' EXIT trap 'rmdir "${LOCK_DIR}" 2>/dev/null || true' EXIT
local update_output update_status
update_output="$("${BREW_BIN}" update 2>&1)" update_output="$("${BREW_BIN}" update 2>&1)"
update_status=$? update_status=$?
if [ "${update_status}" -ne 0 ]; then if (( update_status != 0 )); then
write_error "$(printf '%s\n' "${update_output}" | tail -n 1)" write_error "$(printf '%s\n' "${update_output}" | tail -n 1)"
record_check_time record_check_time
return 1 return 1
@@ -240,66 +254,74 @@ perform_daily_check() {
} }
record_check_time record_check_time
if is_auto_enabled && [ "$(outdated_count)" -gt 0 ]; then if is_auto_enabled && (( $(outdated_count) > 0 )); then
run_upgrade_all auto run_upgrade_all auto
fi fi
} }
check_is_due() { check_is_due() {
[ ! -f "${CHECKED_AT_FILE}" ] && return 0 [[ ! -f "${CHECKED_AT_FILE}" ]] && return 0
checked_at="$(cat "${CHECKED_AT_FILE}" 2>/dev/null || printf '0')" local checked_at
checked_at="$(<"${CHECKED_AT_FILE}" 2>/dev/null || printf '0')"
case "${checked_at}" in case "${checked_at}" in
*[!0-9]*|'') return 0 ;; *[!0-9]*|'') return 0 ;;
esac esac
local now
now="$(date +%s)" now="$(date +%s)"
[ $((now - checked_at)) -ge "${CHECK_INTERVAL_SECONDS}" ] (( now - checked_at >= CHECK_INTERVAL_SECONDS ))
} }
file_line_count() { file_line_count() {
if [ -s "$1" ]; then if [[ -s "$1" ]]; then
wc -l < "$1" | tr -d ' ' local count
count="$(wc -l < "$1")"
printf '%s' "${count// /}"
else else
printf '0' printf '0'
fi fi
} }
outdated_count() { outdated_count() {
local formula_count cask_count
formula_count="$(file_line_count "${FORMULAE_FILE}")" formula_count="$(file_line_count "${FORMULAE_FILE}")"
cask_count="$(file_line_count "${CASKS_FILE}")" cask_count="$(file_line_count "${CASKS_FILE}")"
printf '%s' $((formula_count + cask_count)) printf '%s' $((formula_count + cask_count))
} }
format_checked_at() { format_checked_at() {
if [ ! -f "${CHECKED_AT_FILE}" ]; then if [[ ! -f "${CHECKED_AT_FILE}" ]]; then
printf 'Never' printf 'Never'
return return
fi fi
checked_at="$(cat "${CHECKED_AT_FILE}")" local checked_at
date -r "${checked_at}" '+%Y-%m-%d %H:%M' 2>/dev/null || printf 'Unknown' checked_at="$(<"${CHECKED_AT_FILE}")"
strftime '%Y-%m-%d %H:%M' "${checked_at}" 2>/dev/null || printf 'Unknown'
} }
render_package_menu() { render_package_menu() {
package_file="$1" local package_file="$1"
package_type="$2" local package_type="$2"
heading="$3" local heading="$3"
[ -s "${package_file}" ] || return 0 [[ -s "${package_file}" ]] || return 0
local script_path
script_path="$(escape_attribute "${PLUGIN_PATH}")" script_path="$(escape_attribute "${PLUGIN_PATH}")"
if [ "${package_type}" = "formula" ]; then if [[ "${package_type}" = "formula" ]]; then
heading_symbol="shippingbox" local heading_symbol="shippingbox"
package_symbol="cube.box" local package_symbol="cube.box"
else else
heading_symbol="macwindow" local heading_symbol="macwindow"
package_symbol="app" local package_symbol="app"
fi fi
printf ':%s: %s | sfsize=%s\n' "${heading_symbol}" "${heading}" "${SF_SIZE}" printf ':%s: %s | sfsize=%s\n' "${heading_symbol}" "${heading}" "${SF_SIZE}"
local package_name safe_name
while IFS= read -r package_name; do while IFS= read -r package_name; do
[ -n "${package_name}" ] || continue [[ -n "${package_name}" ]] || continue
safe_name="$(escape_swiftbar "${package_name}")" safe_name="$(escape_swiftbar "${package_name}")"
if is_brew_busy; then if is_brew_busy; then
printf -- '--:%s: %s | sfsize=%s disabled=true\n' "${package_symbol}" "${safe_name}" "${SF_SIZE}" printf -- '--:%s: %s | sfsize=%s disabled=true\n' "${package_symbol}" "${safe_name}" "${SF_SIZE}"
@@ -311,13 +333,14 @@ render_package_menu() {
} }
render_menu() { render_menu() {
local count checked_at script_path
count="$(outdated_count)" count="$(outdated_count)"
checked_at="$(format_checked_at)" checked_at="$(format_checked_at)"
script_path="$(escape_attribute "${PLUGIN_PATH}")" script_path="$(escape_attribute "${PLUGIN_PATH}")"
if is_brew_busy; then if is_brew_busy; then
printf ':arrow.triangle.2.circlepath: … | sfsize=%s tooltip="Homebrew operation in progress…"\n' "${SF_SIZE}" printf ':arrow.triangle.2.circlepath: … | sfsize=%s tooltip="Homebrew operation in progress…"\n' "${SF_SIZE}"
elif [ "${count}" -eq 0 ]; then elif (( count == 0 )); then
printf ':shippingbox.fill: 0 | sfsize=%s tooltip="Homebrew is up to date"\n' "${SF_SIZE}" printf ':shippingbox.fill: 0 | sfsize=%s tooltip="Homebrew is up to date"\n' "${SF_SIZE}"
else else
printf ':shippingbox.fill: %s | sfsize=%s tooltip="%s Homebrew package(s) outdated"\n' "${count}" "${SF_SIZE}" "${count}" printf ':shippingbox.fill: %s | sfsize=%s tooltip="%s Homebrew package(s) outdated"\n' "${count}" "${SF_SIZE}" "${count}"
@@ -328,7 +351,8 @@ render_menu() {
printf -- '--:number.circle: Outdated: %s | sfsize=%s\n' "${count}" "${SF_SIZE}" printf -- '--:number.circle: Outdated: %s | sfsize=%s\n' "${count}" "${SF_SIZE}"
printf -- '--:clock: Last check: %s | sfsize=%s\n' "${checked_at}" "${SF_SIZE}" printf -- '--:clock: Last check: %s | sfsize=%s\n' "${checked_at}" "${SF_SIZE}"
if [ -s "${ERROR_FILE}" ]; then if [[ -s "${ERROR_FILE}" ]]; then
local error_message
error_message="$(escape_swiftbar "$(tail -n 1 "${ERROR_FILE}")")" error_message="$(escape_swiftbar "$(tail -n 1 "${ERROR_FILE}")")"
printf -- '--:exclamationmark.triangle.fill: Last check failed: %s | sfsize=%s color=#FF3B30\n' "${error_message}" "${SF_SIZE}" printf -- '--:exclamationmark.triangle.fill: Last check failed: %s | sfsize=%s color=#FF3B30\n' "${error_message}" "${SF_SIZE}"
fi fi
@@ -346,7 +370,7 @@ render_menu() {
printf ':arrow.clockwise: Check for updates now | sfsize=%s bash="%s" param1=--check-now terminal=false refresh=true\n' "${SF_SIZE}" "${script_path}" printf ':arrow.clockwise: Check for updates now | sfsize=%s bash="%s" param1=--check-now terminal=false refresh=true\n' "${SF_SIZE}" "${script_path}"
fi fi
if [ "${count}" -gt 0 ]; then if (( count > 0 )); then
if is_brew_busy; then if is_brew_busy; then
printf ':arrow.up: Upgrade all in background | sfsize=%s disabled=true\n' "${SF_SIZE}" printf ':arrow.up: Upgrade all in background | sfsize=%s disabled=true\n' "${SF_SIZE}"
else else
@@ -357,14 +381,15 @@ render_menu() {
render_package_menu "${CASKS_FILE}" cask 'Casks' render_package_menu "${CASKS_FILE}" cask 'Casks'
fi fi
if [ -f "${LOG_FILE}" ]; then if [[ -f "${LOG_FILE}" ]]; then
local log_path
log_path="$(escape_attribute "${LOG_FILE}")" log_path="$(escape_attribute "${LOG_FILE}")"
printf -- '---\n' printf -- '---\n'
printf ':doc.text: Open upgrade log | sfsize=%s bash=/usr/bin/open param1="%s" terminal=false\n' "${SF_SIZE}" "${log_path}" printf ':doc.text: Open upgrade log | sfsize=%s bash=/usr/bin/open param1="%s" terminal=false\n' "${SF_SIZE}" "${log_path}"
fi fi
} }
if [ -z "${BREW_BIN}" ]; then if [[ -z "${BREW_BIN}" ]]; then
printf ':exclamationmark.triangle.fill: ! | sfsize=%s color=#FF3B30\n' "${SF_SIZE}" printf ':exclamationmark.triangle.fill: ! | sfsize=%s color=#FF3B30\n' "${SF_SIZE}"
printf -- '---\nHomebrew was not found in /opt/homebrew or /usr/local.\n' printf -- '---\nHomebrew was not found in /opt/homebrew or /usr/local.\n'
exit 0 exit 0
@@ -372,11 +397,11 @@ fi
case "${1:-}" in case "${1:-}" in
--enable-auto) --enable-auto)
touch "${AUTO_FILE}" : >| "${AUTO_FILE}"
exit 0 exit 0
;; ;;
--disable-auto) --disable-auto)
rm -f "${AUTO_FILE}" rm -f -- "${AUTO_FILE:?}" # noka: ZC1059
exit 0 exit 0
;; ;;
--check-now) --check-now)