bin/z.sh

#!/usr/bin/env bash
# bin/z.sh -- a tiny frecency-based directory jumper.
#
# I used rupa/z for years and finally wanted something I could audit on
# my own machine. This is roughly the same idea: track cd targets in a
# flat file, score them by frecency, jump with `z <substring>`.
#
# Source this file from .zshrc (or .bashrc):
#   source "$HOME/.dotfiles/bin/z.sh"

: "${_Z_DATA:=${XDG_DATA_HOME:-$HOME/.local/share}/z/history}"
: "${_Z_CMD:=z}"
: "${_Z_MAX_SCORE:=9000}"

mkdir -p -- "$(dirname -- "$_Z_DATA")"
touch -- "$_Z_DATA"

_z_add() {
  # Record cwd on every prompt. Skip $HOME and /tmp to keep the list signal-y.
  local pwd=$PWD
  case $pwd in
    $HOME|/|/tmp|/tmp/*) return ;;
  esac

  local tmp
  tmp=$(mktemp "${_Z_DATA}.XXXXXX") || return
  local now rank time
  now=$(date +%s)

  awk -v target="$pwd" -v now="$now" -v maxscore="$_Z_MAX_SCORE" '
    BEGIN { FS = "|"; OFS = "|"; seen = 0; total = 0 }
    NF == 3 {
      if ($1 == target) {
        $2 += 1
        $3 = now
        seen = 1
      }
      print
      total += $2
    }
    END {
      if (!seen) print target, 1, now
      # Age the table down proportionally if it is getting heavy.
      if (total > maxscore) {
        # signal back via exit status; caller will trigger an age pass.
        exit 77
      }
    }
  ' "$_Z_DATA" > "$tmp"
  local rc=$?
  if (( rc == 77 )); then
    awk -F'|' -v OFS='|' '{ $2 = $2 * 0.9; print }' "$tmp" > "$_Z_DATA"
    rm -f -- "$tmp"
  else
    mv -f -- "$tmp" "$_Z_DATA"
  fi
}

_z_frecency() {
  # Args: rank, last-visit timestamp, now.
  # Stolen-by-reference from rupa/z's formula.
  local rank=$1 ts=$2 now=$3
  local dx=$(( now - ts ))
  if (( dx < 3600 ));        then awk -v r="$rank" 'BEGIN { print r * 4 }'
  elif (( dx < 86400 ));     then awk -v r="$rank" 'BEGIN { print r * 2 }'
  elif (( dx < 604800 ));    then awk -v r="$rank" 'BEGIN { print r / 2 }'
  else                            awk -v r="$rank" 'BEGIN { print r / 4 }'
  fi
}

_z_search() {
  local pattern=$*
  local now; now=$(date +%s)
  awk -v pat="$pattern" -v now="$now" '
    BEGIN { FS = "|"; OFS = "|" }
    NF == 3 && index($1, pat) {
      dx = now - $3
      if      (dx < 3600)   s = $2 * 4
      else if (dx < 86400)  s = $2 * 2
      else if (dx < 604800) s = $2 / 2
      else                   s = $2 / 4
      print s, $1
    }
  ' "$_Z_DATA" | sort -nr | awk '{ $1 = ""; sub(/^ /, ""); print }'
}

"$_Z_CMD"() {
  if [[ $# -eq 0 ]]; then
    _z_search '' | head -n 20
    return
  fi
  case $1 in
    -l) shift; _z_search "$*"; return ;;
    -c) : > "$_Z_DATA"; return ;;
    -h|--help)
      cat <<HELP
usage: z <substring>        jump to best match
       z -l <substring>     list matches, best first
       z -c                 clear history
HELP
      return ;;
  esac

  local match
  match=$(_z_search "$*" | head -n 1)
  if [[ -z $match ]]; then
    print -r -- "z: no match for '$*'" >&2
    return 1
  fi
  if [[ ! -d $match ]]; then
    # Clean up stale entries so the next lookup finds something real.
    local tmp; tmp=$(mktemp "${_Z_DATA}.XXXXXX") || return
    awk -v bad="$match" 'BEGIN{FS=OFS="|"} $1 != bad' "$_Z_DATA" > "$tmp"
    mv -f -- "$tmp" "$_Z_DATA"
    print -r -- "z: pruned missing dir '$match'" >&2
    return 1
  fi
  cd -- "$match"
}

# Hook the recorder into the shell's per-prompt callbacks.
if [[ -n $ZSH_VERSION ]]; then
  autoload -Uz add-zsh-hook
  add-zsh-hook chpwd _z_add
elif [[ -n $BASH_VERSION ]]; then
  case "$PROMPT_COMMAND" in
    *_z_add*) : ;;
    *) PROMPT_COMMAND="_z_add;${PROMPT_COMMAND:-}" ;;
  esac
fi