Zsh Configuration and Plugins - Part Two

The second post in the series on ZSH / shell configuration and customisation

Series - ZSH

Zsh Configuration and Plugins - Part two

You can find the first post in this series here.


Functions

Many of my functions start out as aliases and then I convert them to functions when I need to add more functionality. You can find these in 9-function.rc.


pdfcompress ()
{
  gs -q -dNOPAUSE -dBATCH -dSAFER -sDEVICE=pdfwrite -dCompatibilityLevel=1.3 -dPDFSETTINGS=/screen -dEmbedAllFonts=true -dSubsetFonts=true -dColorImageDownsampleType=/Bicubic -dColorImageResolution=144 -dGrayImageDownsampleType=/Bicubic -dGrayImageResolution=144 -dMonoImageDownsampleType=/Bicubic -dMonoImageResolution=144 -sOutputFile="$1".compressed.pdf "$1";
}
function echoerr() {
  printf "%s\n" "$*" >&2;
}

Interactive cd using fzf

function fcd() {
  local dir;

  while true; do
    # exit with ^D
    dir="$(ls -a1p | grep '/$' | grep -v '^./$' | fzf --height 40% --reverse --no-multi --preview 'pwd' --preview-window=up,1,border-none --no-info)"
    if [[ -z "${dir}" ]]; then
      break
    else
      cd "${dir}" || exit
    fi
  done
}

Touches all files passed in as arguments, if the directory specified doesn’t exist it will be created.

tch() {
  for x in "$@"; do
    __mkdir "${x:h}"
  done
  touch "$@"
}

Authenticates to AWS using Azure AD and sets the profile.

function aws-azure-login() {
  command aws-azure-login --no-prompt --profile "$@"
  export AWS_PROFILE=$@
  export AWSCLIPARAMS="--profile=$@"
}

Authenticates to AWS using saml2aws and sets the profile.

function s2a { eval $($(which saml2aws) script --shell=bash --profile=$@); }
function awslogin() {
  if [ -z "${1}" ]; then
    echo "ERROR: account name required, e.g. awslogin data-dev"
  else
    # Check credentials are current, refresh if needed and export into shell
    aws configure list --profile "${1}" && eval $(saml2aws script --profile "${1}")
  fi
}

File with generated password using age.

encrypt_file_pw() {
  if ! command -v age &> /dev/null; then
    echo "age could not be found. Install it with 'brew install age'"
    return
  else
    age -p "$1" -o "${1}.age"
  fi
}

Reads a password from keychain and outputs it

Usage: keychain_password <service name to match on> <account>

keychain_password() {
  # Make sure there no screen recording active on before returning the password
  if [[ $(pgrep -i screencapture) ]]; then
    echo "ERROR: macOS screen recording (screencapture process) is active. Please stop it before printing the password."
    return 1
  fi
  security find-generic-password -s "$1" -a "$(whoami)" -w
}

Prompts for a name and a password and stores it in keychain

keychain_password_prompt() {
  echo "Enter a name for the password:"
  read -r name
  echo "Enter the password:"
  # disable echoing the password
  stty -echo
  read -r password
  security add-generic-password -s "$name" -a "$(whoami)" -w "$password"
}

A function that checks ssh-add and adds my keys if they’re not already added

function ssh-add-keys() {
  if ! ssh-add -l|grep -qe 'ED25519\|RSA'; then
    ssh-add --apple-use-keychain ~/.ssh/id_*.key
  fi
}

List env variables with fzf

list_env() {
  var=$(printenv | cut -d= -f1 | fzf) \
    && echo "$var=$(printenv "$var")" \
    && unset var
}
function update_asdf() {
  asdf update
  asdf plugin-update --all
}

Creates a directory if it doesn’t exist.

  • Alias: mkcd
__mkdir() { if [[ ! -d $1 ]]; then mkdir -p "$1"; fi }

Usage: mv oldfilename

If you call mv without the second parameter it will prompt you to edit the filename on command line, useful when you want to change just a few letters in a long name.

function mv() {
  if [ "$#" -ne 1 ]; then
    command mv "$@"
    return
  fi
  if [ ! -f "$1" ]; then
    command file "$@"
    return
  fi

  read -ei "$1" newfilename
  mv -v "$1" "$newfilename"
}
clean_string() {
    # Escape special characters in a string such as $, ", ', `, \, and newline.
    # Usage: escape_string "string to escape"
    local string="${1}"
    local escaped_string
    escaped_string=$(printf '%q' "${string}")
    echo "${escaped_string}"
}

Adds, commits and pushes all changes in the current directory with the first argument as the commit message

function gitlazy() {
  git add .
  git commit -a -m "$1"
  git push
}

Checks out a branch when provided a branch name, or will prompt to select a branch using fzf.

/2023/11/zsh-configuration-and-plugins-part-two/gco.png

function gco(){
  if [[ -n $1 ]]; then
    if [[ $1 == "-b" ]]; then
      # shift to remove -b from args
      shift 1
    fi
    # check to see if the branch exists, if it doesn't offer to create it and check it out otherwise just check it out
    if git branch -a | grep -q "remotes/origin/$1"; then
      git checkout "$1"
    else
      echo "Creating branch $1"
      git checkout -b "$1"
    fi
  else
    git branch --sort=-committerdate | fzf --header 'Checkout Recent Branch' --preview 'git diff --color=always {1}' --pointer='>' | xargs git checkout
  fi
}

Deletes a branch when provided a branch name, or will prompt to select a branch to delete using fzf.

/2023/11/zsh-configuration-and-plugins-part-two/gbd.png

function gbd(){
  if [[ -n $* ]]; then
    git branch -d "$@"
  else
    git branch --sort=-committerdate | fzf --header 'Delete Git Branch' --preview 'git diff --color=always {1}' --pointer='>' | xargs git branch delete
  fi
}

Checks out a hotfix branch with the current date and user.

  • Alias: coh
function checkout-hotfix() {
  git checkout -b "Hotfix-$(date +%Y-%m-%d)-${USER}"
}

Commits a hotfix branch with the current date and user.

  • Alias: ch
function commit-hotfix() {
  git add .
  git commit -m "Hotfix-$(date +%Y/%m/%d): $*"
}

Checks out a branch with the provided JIRA ticket number along with the current date and user.

  • Alias: coj
function checkout-jira() {
  JIRA_TICKET_PREFIX="IF"
  git checkout -b "${JIRA_TICKET_PREFIX}-${1}-${USER}-$(date +%Y-%m-%d)"
}

Takes the JIRA card number from the start of the branch name for the commit message (e.g. IF-1234 from the branch IF-1234-feature-name)

  • Alias: cj.
function commit-jira(){
  git add .
  git commit -m "$(git rev-parse --abbrev-ref HEAD | cut -d'-' -f1 -f2) -- $*"
}

Checks out a pull request from GitHub using the GitHub CLI.

function pr-checkout() {
  local jq_template pr_number

  jq_template='"'\
'#\(.number) - \(.title)'\
'\t'\
'Author: \(.user.login)\n'\
'Created: \(.created_at)\n'\
'Updated: \(.updated_at)\n\n'\
'\(.body)'\
'"'

  pr_number=$(
    gh api 'repos/:owner/:repo/pulls' |
    jq ".[] | $jq_template" |
    sed -e 's/"\(.*\)"/\1/' -e 's/\\t/\t/' |
    fzf \
      --with-nth=1 \
      --delimiter='\t' \
      --preview='echo -e {2}' \
      --preview-window=top:wrap |
    sed 's/^#\([0-9]\+\).*/\1/'
  )

  if [ -n "$pr_number" ]; then
    gh pr checkout "$pr_number"
  fi
}

Git checkout new branch, git add, git commit, git push in all subdirectories matching a pattern

  • Alias: gacp
function git_add_commit_push(){
  if [[ -z $1 ]] || [[ -z "$2" ]] || [[ -z "$3" ]]; then
    echo 'You must pass three paramters, branchname, commit message, dir match - e.g. "my-branch" "commit message" ABC*';
  fi
  BRANCHNAME="$1"
  COMMITNAME="$2"
  MATCHDIRS="$3"
  for dir in $MATCHDIRS;
    do (
      cd "$dir" &&
      git checkout -b "$BRANCHNAME" &&
      git add . &&
      git commit -n -m "$COMMITNAME" &&
      git push
      )
    done
}

Deletes workflow logs from a given repo older than 1 month

Usage: USER=myuser REPO=myrepo ghac

function ghac() {
  DATE=$(date -v "-1m" +"%Y-%m-%d") gh api "repos/${USER}/${REPO}/actions/runs" --paginate -q '.workflow_runs[] | select (.run_started_at  <= "env.DATE") | (.id)' \
    | xargs -n1 -I % gh api "repos/${USER}/${REPO}/actions/runs"/% -X DELETE
}

The following Git functions are mainly consumed by other functions or the prompt.

function git_prompt_info() {
  local ref
  if [[ "$(command git config --get oh-my-zsh.hide-status 2>/dev/null)" != "1" ]]; then
    ref=$(command git symbolic-ref HEAD 2> /dev/null) || \
    ref=$(command git rev-parse --short HEAD 2> /dev/null) || return 0
    echo "$ZSH_THEME_GIT_PROMPT_PREFIX${ref#refs/heads/}$(parse_git_dirty)$ZSH_THEME_GIT_PROMPT_SUFFIX"
  fi
}

Outputs the name of the current branch

function git_current_branch() {
  local ref
  ref=$(command git symbolic-ref --quiet HEAD 2> /dev/null)
  local ret=$?
  if [[ $ret != 0 ]]; then
    [[ $ret == 128 ]] && return  # no git repo.
    ref=$(command git rev-parse --short HEAD 2> /dev/null) || return
  fi
  echo "${ref#refs/heads/}"
}

Gets the number of commits ahead from remote

function git_commits_ahead() {
  if command git rev-parse --git-dir &>/dev/null; then
    local commits
    commits="$(git rev-list --count @{upstream}..HEAD)"
    if [[ "$commits" != 0 ]]; then
      echo "$ZSH_THEME_GIT_COMMITS_AHEAD_PREFIX$commits$ZSH_THEME_GIT_COMMITS_AHEAD_SUFFIX"
    fi
  fi
}

Gets the number of commits behind remote

function git_commits_behind() {
  if command git rev-parse --git-dir &>/dev/null; then
    local commits
    commits="$(git rev-list --count HEAD..@{upstream})"
    if [[ "$commits" != 0 ]]; then
      echo "$ZSH_THEME_GIT_COMMITS_BEHIND_PREFIX$commits$ZSH_THEME_GIT_COMMITS_BEHIND_SUFFIX"
    fi
  fi
}

Outputs if current branch is ahead of remote

function git_prompt_ahead() {
  if [[ -n "$(command git rev-list origin/"$(git_current_branch)"..HEAD 2> /dev/null)" ]]; then
    echo "$ZSH_THEME_GIT_PROMPT_AHEAD"
  fi
}

Outputs if current branch is behind remote

function git_prompt_behind() {
  if [[ -n "$(command git rev-list HEAD..origin/"$(git_current_branch)" 2> /dev/null)" ]]; then
    echo "$ZSH_THEME_GIT_PROMPT_BEHIND"
  fi
}

Outputs if current branch exists on remote or not

function git_prompt_remote() {
  if [[ -n "$(command git show-ref origin/"$(git_current_branch)" 2> /dev/null)" ]]; then
    echo "$ZSH_THEME_GIT_PROMPT_REMOTE_EXISTS"
  else
    echo "$ZSH_THEME_GIT_PROMPT_REMOTE_MISSING"
  fi
}

Formats prompt string for current git commit short SHA

function git_prompt_short_sha() {
  local SHA
  SHA=$(command git rev-parse --short HEAD 2> /dev/null) && echo "$ZSH_THEME_GIT_PROMPT_SHA_BEFORE$SHA$ZSH_THEME_GIT_PROMPT_SHA_AFTER"
}

Formats prompt string for current git commit long SHA

function git_prompt_long_sha() {
  local SHA
  SHA=$(command git rev-parse HEAD 2> /dev/null) && echo "$ZSH_THEME_GIT_PROMPT_SHA_BEFORE$SHA$ZSH_THEME_GIT_PROMPT_SHA_AFTER"
}

Get the status of the working tree

function git_prompt_status() {
  local INDEX STATUS
  INDEX=$(command git status --porcelain -b 2> /dev/null)
  STATUS=""
  if eval "$(echo "$INDEX" | command grep -E '^\?\? ' &> /dev/null)"; then
    STATUS="$ZSH_THEME_GIT_PROMPT_UNTRACKED$STATUS"
  fi
  if eval "$(echo "$INDEX" | grep '^A  ' &> /dev/null)"; then
    STATUS="$ZSH_THEME_GIT_PROMPT_ADDED$STATUS"
  elif eval "$(echo "$INDEX" | grep '^M  ' &> /dev/null)"; then
    STATUS="$ZSH_THEME_GIT_PROMPT_ADDED$STATUS"
  fi
  if eval "$(echo "$INDEX" | grep '^ M ' &> /dev/null)"; then
    STATUS="$ZSH_THEME_GIT_PROMPT_MODIFIED$STATUS"
  elif eval "$(echo "$INDEX" | grep '^AM ' &> /dev/null)"; then
    STATUS="$ZSH_THEME_GIT_PROMPT_MODIFIED$STATUS"
  elif eval "$(echo "$INDEX" | grep '^ T ' &> /dev/null)"; then
    STATUS="$ZSH_THEME_GIT_PROMPT_MODIFIED$STATUS"
  fi
  if eval "$(echo "$INDEX" | grep '^R  ' &> /dev/null)"; then
    STATUS="$ZSH_THEME_GIT_PROMPT_RENAMED$STATUS"
  fi
  if eval "$(echo "$INDEX" | grep '^ D ' &> /dev/null)"; then
    STATUS="$ZSH_THEME_GIT_PROMPT_DELETED$STATUS"
  elif eval "$(echo "$INDEX" | grep '^D  ' &> /dev/null)"; then
    STATUS="$ZSH_THEME_GIT_PROMPT_DELETED$STATUS"
  elif eval "$(echo "$INDEX" | grep '^AD ' &> /dev/null)"; then
    STATUS="$ZSH_THEME_GIT_PROMPT_DELETED$STATUS"
  fi
  if eval "$(command git rev-parse --verify refs/stash >/dev/null 2>&1)"; then
    STATUS="$ZSH_THEME_GIT_PROMPT_STASHED$STATUS"
  fi
  if eval "$(echo "$INDEX" | grep '^UU ' &> /dev/null)"; then
    STATUS="$ZSH_THEME_GIT_PROMPT_UNMERGED$STATUS"
  fi
  if eval "$(echo "$INDEX" | grep '^## [^ ]\+ .*ahead' &> /dev/null)"; then
    STATUS="$ZSH_THEME_GIT_PROMPT_AHEAD$STATUS"
  fi
  if eval "$(echo "$INDEX" | grep '^## [^ ]\+ .*behind' &> /dev/null)"; then
    STATUS="$ZSH_THEME_GIT_PROMPT_BEHIND$STATUS"
  fi
  if eval "$(echo "$INDEX" | grep '^## [^ ]\+ .*diverged' &> /dev/null)"; then
    STATUS="$ZSH_THEME_GIT_PROMPT_DIVERGED$STATUS"
  fi
  echo "$STATUS"
}

Compares the provided version of git to the version installed and on path outputs -1, 0, or 1 if the installed version is less than, equal to, or greater than the input version, respectively

function git_compare_version() {
  local INPUT_GIT_VERSION INSTALLED_GIT_VERSION
  INPUT_GIT_VERSION=(${(s/./)1})
  INSTALLED_GIT_VERSION=($(command git --version 2>/dev/null))
  INSTALLED_GIT_VERSION=(${(s/./)INSTALLED_GIT_VERSION[3]})

  for i in {1..3}; do
    if [[ ${INSTALLED_GIT_VERSION[$i]} -gt ${INPUT_GIT_VERSION[$i]} ]]; then
      echo 1
      return 0
    fi
    if [[ ${INSTALLED_GIT_VERSION[$i]} -lt ${INPUT_GIT_VERSION[$i]} ]]; then
      echo -1
      return 0
    fi
  done
  echo 0
}

Outputs the name of the current user

function git_current_user_name() {
  command git config user.name 2>/dev/null
}

Outputs the email of the current user

function git_current_user_email() {
  command git config user.email 2>/dev/null
}

Related Content