zsh/.functions.zsh

# zsh/.functions.zsh -- small shell functions I actually use day to day.
# Kept out of .aliases because these need positional args or control flow.
# Loaded from .zshrc via: source "$ZDOTDIR/.functions.zsh"

# mkcd <dir>: make and enter a directory in one step.
mkcd() {
  [[ -n $1 ]] || { print -r -- "usage: mkcd <dir>" >&2; return 2; }
  mkdir -p -- "$1" && cd -- "$1"
}

# up [n]: go up n directories (default 1). `up 3` ~= `cd ../../..`.
up() {
  local n=${1:-1} path=""
  for (( i = 0; i < n; i++ )); do
    path+="../"
  done
  cd -- "$path" || return
}

# gcb <branch>: git create-branch. Creates from origin/main by default so
# I stop accidentally branching off whatever is currently checked out.
gcb() {
  [[ -n $1 ]] || { print -r -- "usage: gcb <branch> [base]" >&2; return 2; }
  local base=${2:-origin/main}
  git fetch --quiet origin main
  git switch -c "$1" "$base"
}

# gclean: delete every merged local branch except main/master/current.
gclean() {
  local current
  current=$(git symbolic-ref --quiet --short HEAD) || return
  git branch --merged main 2>/dev/null \
    | grep -vE "^\*|^\+| (main|master|$current)\$" \
    | xargs -r git branch -d
}

# glog [n]: one-line git log with color, default 20 entries.
glog() {
  git log --oneline --decorate --color=auto -n "${1:-20}"
}

# grep-ls: rg files matching a pattern, one path per line, deduplicated.
grep-ls() {
  [[ -n $1 ]] || { print -r -- "usage: grep-ls <pattern>" >&2; return 2; }
  rg --files-with-matches --no-messages "$@"
}

# extract <file>: unpack most archive formats without remembering the flags.
extract() {
  [[ -f $1 ]] || { print -r -- "extract: $1 is not a file" >&2; return 2; }
  case $1 in
    *.tar.bz2|*.tbz2) tar xjf "$1" ;;
    *.tar.gz|*.tgz)   tar xzf "$1" ;;
    *.tar.xz|*.txz)   tar xJf "$1" ;;
    *.tar.zst)        tar --zstd -xf "$1" ;;
    *.tar)            tar xf "$1" ;;
    *.zip)            unzip "$1" ;;
    *.rar)            unrar x "$1" ;;
    *.7z)             7z x "$1" ;;
    *.gz)             gunzip "$1" ;;
    *.bz2)            bunzip2 "$1" ;;
    *.xz)             unxz "$1" ;;
    *.Z)              uncompress "$1" ;;
    *) print -r -- "extract: don't know how to open $1" >&2; return 2 ;;
  esac
}

# mkpass [n]: printable random password of length n (default 24).
mkpass() {
  local n=${1:-24}
  LC_ALL=C tr -dc 'A-Za-z0-9!@#%^&*_+-' </dev/urandom | head -c "$n"
  print
}

# port <n>: show what is listening on TCP port n. Linux + macOS friendly.
port() {
  [[ -n $1 ]] || { print -r -- "usage: port <number>" >&2; return 2; }
  if (( $+commands[ss] )); then
    ss -ltnp "sport = :$1" 2>/dev/null
  else
    lsof -iTCP:"$1" -sTCP:LISTEN -P -n 2>/dev/null
  fi
}

# fcd: fzf-based cd, bound to alt-c in the keymap.
fcd() {
  local dir
  dir=$(
    fd --type d --hidden --follow --exclude .git 2>/dev/null \
      | fzf --prompt='cd> ' --height=40% --reverse
  ) || return
  cd -- "$dir"
}

# serve [port]: one-off python static server with a sensible default.
serve() {
  local port=${1:-8080}
  print -r -- "serving $PWD on http://localhost:$port"
  python3 -m http.server "$port"
}

# json <file>: pretty-print json, fall back to python if jq is missing.
json() {
  if (( $+commands[jq] )); then
    jq . "${1:-/dev/stdin}"
  else
    python3 -m json.tool "${1:-/dev/stdin}"
  fi
}

# manp <cmd>: open a man page in preview with nice typography.
manp() {
  [[ -n $1 ]] || { print -r -- "usage: manp <command>" >&2; return 2; }
  man -t "$1" | open -f -a Preview
}

# weather [city]: quick weather via wttr.in -- no keys required.
weather() {
  local q=${1:-}
  curl -fsSL "https://wttr.in/${q}?format=3"
}

# mvcd <src>: mv a file and cd to the destination dir.
mvcd() {
  [[ $# -ge 2 ]] || { print -r -- "usage: mvcd <src>... <dst>" >&2; return 2; }
  mv "$@" && cd -- "${@: -1}"
}

# note: append a line to today's TIL file; picks up my tilstream layout.
note() {
  local d=${TIL_ROOT:-$HOME/til}/posts
  mkdir -p -- "$d"
  local f="$d/$(date +%Y-%m-%d).md"
  [[ -s $f ]] || print "---\ntitle: $(date +%Y-%m-%d)\ndate: $(date +%Y-%m-%d)\n---\n" >> "$f"
  print -r -- "- $*" >> "$f"
  print -r -- "appended to $f"
}