159 lines
6.1 KiB
Plaintext
Executable File
159 lines
6.1 KiB
Plaintext
Executable File
#autoload
|
|
# compinit_doctor — inspect, audit, diff, and (optionally) rebuild zsh completion state
|
|
# Usage: compinit_doctor [--status] [--audit] [--rebuild] [--snap FILE] [--diff FILE] [--dump PATH] [--json] [--help]
|
|
|
|
compinit_doctor() {
|
|
emulate -L zsh
|
|
setopt extended_glob
|
|
zmodload -F zsh/datetime 2>/dev/null || true
|
|
|
|
# -------- defaults --------
|
|
local dump="${ZSH_COMPDUMP:-${XDG_CACHE_HOME:-$HOME/.cache}/zcompdump}"
|
|
local action=status json=0 snap_file="" diff_file=""
|
|
|
|
# -------- arg parse --------
|
|
while (( $# > 0 )); do
|
|
case "$1" in
|
|
--help|-h) action=help;;
|
|
--status|-s) action=status;;
|
|
--audit|-a) action=audit;;
|
|
--rebuild|-r) action=rebuild;;
|
|
--snap|-S) shift; snap_file="${1:-}"; [[ -z $snap_file ]] && { print -ru2 "compinit_doctor: --snap requires a file"; return 2; }; action=snap;;
|
|
--diff|-d) shift; diff_file="${1:-}"; [[ -z $diff_file ]] && { print -ru2 "compinit_doctor: --diff requires a file"; return 2; }; action=diff;;
|
|
--dump=*|-D=*) dump="${1#*=}";;
|
|
--dump|-D) shift; dump="${1:-$dump}";;
|
|
--json|-j) json=1;;
|
|
*) print -ru2 "compinit_doctor: unknown option: $1"; return 2;;
|
|
esac
|
|
shift
|
|
done
|
|
|
|
# -------- helpers --------
|
|
__mtime() {
|
|
[[ -e $1 ]] && stat -c %Y -- "$1" 2>/dev/null || return 1
|
|
}
|
|
__age_human() {
|
|
local mt now delta
|
|
mt="$(__mtime "$1")" || { print -r -- "—"; return 0; }
|
|
now="${EPOCHSECONDS:-$(date +%s)}"
|
|
delta=$(( now - mt )); (( delta < 0 )) && delta=0
|
|
printf "%dd %02dh %02dm" $((delta/86400)) $(((delta%86400)/3600)) $(((delta%3600)/60))
|
|
}
|
|
__exists() { [[ -e $1 ]] && print yes || print no; }
|
|
__count_comps() { (( ${+_comps} )) && print ${#_comps} || print 0; }
|
|
|
|
__print_status_text() {
|
|
local dump_mtime dump_age compiled="${dump}.zwc"
|
|
dump_mtime="$(__mtime "$dump" 2>/dev/null)" || dump_mtime="—"
|
|
dump_age="$(__age_human "$dump")"
|
|
|
|
print -r -- "compinit status:"
|
|
print -r -- " dump path : $dump"
|
|
print -r -- " dump exists : $(__exists "$dump")"
|
|
print -r -- " dump age : $dump_age"
|
|
print -r -- " compiled exists : $(__exists "$compiled")"
|
|
print -r -- " \$fpath entries : ${#fpath}"
|
|
print -r -- " \$fpath missing : $(
|
|
local -a miss d
|
|
for d in $fpath; do [[ -d $d ]] || miss+=("$d"); done
|
|
print ${#miss}
|
|
)"
|
|
print -r -- " completion funcs : $(__count_comps)"
|
|
print -r -- " compinit present : $((($+functions[compinit] || $+commands[compinit])) && print "yes" || print "no")"
|
|
}
|
|
|
|
__print_status_json() {
|
|
local -a missing=(); local d
|
|
for d in $fpath; do [[ -d $d ]] || missing+=("$d"); done
|
|
local compinit_present=false
|
|
if (( $+functions[compinit] || $+commands[compinit] )); then compinit_present=true; fi
|
|
|
|
printf '{"dump":"%s","dump_exists":%s,"dump_age":"%s","compiled_exists":%s,' \
|
|
"$dump" "$([[ -e $dump ]] && print true || print false)" "$(__age_human "$dump")" "$([[ -e ${dump}.zwc ]] && print true || print false)"
|
|
printf '"fpath_count":%d,"fpath_missing_count":%d,"completion_funcs":%d,"compinit_present":%s}\n' \
|
|
"${#fpath}" "${#missing}" "$(__count_comps)" "$compinit_present"
|
|
}
|
|
|
|
__do_audit() {
|
|
if ! whence -w compaudit >/dev/null 2>&1; then
|
|
print -ru2 "compinit_doctor: compaudit not available"; return 3
|
|
fi
|
|
local -a insecure
|
|
insecure=(${(f)"$(compaudit 2>/dev/null)"})
|
|
if (( ${#insecure} )); then
|
|
print -r -- "Insecure completion dirs/files:"
|
|
printf " %s\n" "${insecure[@]}"
|
|
print -r -- "\nSuggested fix (review before running):"
|
|
print -r -- " chmod -R go-w -- ${insecure:q}"
|
|
else
|
|
print -r -- "No insecure completion paths found (compaudit)."
|
|
fi
|
|
}
|
|
|
|
__do_rebuild() {
|
|
local t0 t1 dt
|
|
t0="${EPOCHREALTIME:-$(perl -MTime::HiRes=time -e 'printf \"%.6f\", time()')}"
|
|
autoload -U compinit
|
|
compinit -i -d "$dump" || { print -ru2 "compinit_doctor: compinit failed"; return 4; }
|
|
{ zcompile "$dump" 2>/dev/null || true; } &
|
|
t1="${EPOCHREALTIME:-$(perl -MTime::HiRes=time -e 'printf \"%.6f\", time()')}"
|
|
dt=$(awk -v s="$t0" -v e="$t1" 'BEGIN{printf "%.3f",(e-s)*1000}')
|
|
printf 'Rebuilt dump at: %s (compinit %.3fms)\n' "$dump" "$dt"
|
|
}
|
|
|
|
__snap() {
|
|
local file="$1"
|
|
[[ -n $file ]] || { print -ru2 "compinit_doctor: snapshot file required"; return 2; }
|
|
mkdir -p -- "${file:h}" 2>/dev/null
|
|
{
|
|
print "# compinit_doctor snapshot"
|
|
print "# date: $(date +'%Y-%m-%d %H:%M:%S %z')"
|
|
printf "%s\n" "${(o)fpath[@]}"
|
|
} >| "$file"
|
|
print -r -- "Snapshot written: $file"
|
|
}
|
|
|
|
__diff() {
|
|
local file="$1"
|
|
[[ -r $file ]] || { print -ru2 "compinit_doctor: cannot read snapshot: $file"; return 2; }
|
|
local -a snap cur added removed
|
|
snap=(${(f)"$(<"$file")"})
|
|
snap=(${snap:#\#*})
|
|
cur=(${(o)fpath})
|
|
local -Ua _S _C
|
|
_S=("${snap[@]}"); _C=("${cur[@]}")
|
|
local s c
|
|
for s in "${_S[@]}"; do [[ -z ${_C[(r)$s]} ]] && removed+=("$s"); done
|
|
for c in "${_C[@]}"; do [[ -z ${_S[(r)$c]} ]] && added+=("$c"); done
|
|
print -r -- "fpath diff vs $file:"
|
|
print -r -- " added (${#added}):"
|
|
printf " + %s\n" "${added[@]}"
|
|
print -r -- " removed(${#removed}):"
|
|
printf " - %s\n" "${removed[@]}"
|
|
}
|
|
|
|
# -------- dispatch --------
|
|
case "$action" in
|
|
help)
|
|
cat <<'EOF'
|
|
compinit_doctor — inspect, audit, diff, and rebuild zsh completion state
|
|
|
|
--status|-s Show current compinit/zcompdump status (default)
|
|
--audit |-a Run compaudit and list insecure paths
|
|
--rebuild|-r Force compinit -i -d <dump> and background zcompile
|
|
--snap |-S FILE Snapshot current $fpath (sorted) to FILE
|
|
--diff |-d FILE Diff current $fpath against FILE snapshot
|
|
--dump |-D PATH Override zcompdump path (default: $XDG_CACHE_HOME/zcompdump)
|
|
--json |-j Emit status as a single JSON object
|
|
--help |-h Show this help
|
|
EOF
|
|
;;
|
|
audit) __do_audit;;
|
|
rebuild) __do_rebuild;;
|
|
snap) __snap "$snap_file";;
|
|
diff) __diff "$diff_file";;
|
|
status)
|
|
(( json )) && __print_status_json || __print_status_text
|
|
;;
|
|
esac
|
|
} |