200 lines
		
	
	
		
			4.9 KiB
		
	
	
	
		
			Bash
		
	
	
		
			Executable file
		
	
	
	
	
			
		
		
	
	
			200 lines
		
	
	
		
			4.9 KiB
		
	
	
	
		
			Bash
		
	
	
		
			Executable file
		
	
	
	
	
#!/usr/bin/env zsh
 | 
						|
 | 
						|
# Dotfile manager
 | 
						|
#
 | 
						|
# By Henrik Lissner <henrik@lissner.net>
 | 
						|
# https://github.com/hlissner/dotfiles
 | 
						|
#
 | 
						|
# Requires: git, zsh
 | 
						|
 | 
						|
source "${0:A:h}/env"
 | 
						|
SEP='.'
 | 
						|
 | 
						|
# helpers
 | 
						|
function echo-debug { echo "$@" | sed -e "s%$HOME%~%g" -e "s%$DOTFILES%.%g"; }
 | 
						|
function echo-info  { printf "\r\033[2K\033[0;34m[ .. ]\033[0m %s\n" "$*"; }
 | 
						|
function echo-skip  { printf "\r\033[2K\033[38;05;240m[SKIP]\033[0m %s\n" "$*"; }
 | 
						|
function echo-ok    { printf "\r\033[2K\033[0;32m[ OK ]\033[0m %s\n" "$*"; }
 | 
						|
function echo-fail  { printf "\r\033[2K\033[0;31m[FAIL]\033[0m %s\n" "$*"; }
 | 
						|
 | 
						|
function _exec { ${DRYRUN:+echo-debug} $@; return ${DRYRUN:+1}; }
 | 
						|
function _topic-do {
 | 
						|
  if [[ -e "$1/_init" ]]; then
 | 
						|
    if [[ ! -x "$1/_init" ]]; then
 | 
						|
      echo-fail "$1's init script isn't executable"
 | 
						|
      return 1
 | 
						|
    fi
 | 
						|
    export TOPIC=$1
 | 
						|
    pushd -q $1
 | 
						|
    _exec ./_init $2
 | 
						|
    popd -q
 | 
						|
    return $?
 | 
						|
  fi
 | 
						|
}
 | 
						|
function _topics {
 | 
						|
  local topics=( "$DOTFILES_DATA"/*.topic(N) )
 | 
						|
  echo ${${${topics[@]#$DOTFILES_DATA/}%.topic}/$SEP//}
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
# For _init scripts
 | 
						|
function topic-enabled-p { [[ -L "$(topic-path $1)" ]] }
 | 
						|
function topic-path { echo "$DOTFILES_DATA/${1/\//$SEP}.topic" }
 | 
						|
 | 
						|
function mklink {
 | 
						|
  if [[ $1 == '-s' ]]; then
 | 
						|
    local dosudo=1
 | 
						|
    shift
 | 
						|
  fi
 | 
						|
  local flags="-sv${OVERWRITE:+f}"
 | 
						|
  local srcs=( ${@:1:-1} )
 | 
						|
  local dest=${@[-1]}
 | 
						|
  if [[ $dest == */ && ! -d $dest ]]; then
 | 
						|
    mkdir -p $dest
 | 
						|
  elif [[ ! -d ${dest%/*} ]]; then
 | 
						|
    mkdir -p ${dest%/*}
 | 
						|
  fi
 | 
						|
  for lk in ${srcs[@]}; do
 | 
						|
    local src
 | 
						|
    case $lk in
 | 
						|
      /*) src=$lk ;;
 | 
						|
      .)  src="$(topic-path $TOPIC)" ;;
 | 
						|
      *)  src="$(topic-path $TOPIC)/$lk" ;;
 | 
						|
    esac
 | 
						|
    if [[ -d $src ]]; then
 | 
						|
      if [[ $dest != */ && -d $dest && -L $dest ]]; then
 | 
						|
        ${dosudo:+sudo} rm -fv $dest
 | 
						|
      fi
 | 
						|
    fi
 | 
						|
    _exec ${dosudo:+sudo} ln $flags $src $dest
 | 
						|
  done
 | 
						|
}
 | 
						|
 | 
						|
function init {
 | 
						|
  declare -f $1 >/dev/null || return 1
 | 
						|
  echo-info "$TOPIC.init.$1: start"
 | 
						|
  $1
 | 
						|
  local retval=$?
 | 
						|
  echo-ok "$TOPIC.init.$1: done"
 | 
						|
  return $retval
 | 
						|
}
 | 
						|
 | 
						|
# deploy functions
 | 
						|
function topic-init {
 | 
						|
  for topic in $@; do
 | 
						|
    if [[ ! -d $topic ]]; then
 | 
						|
      echo-fail "$topic doesn't exist!"
 | 
						|
      continue
 | 
						|
    fi
 | 
						|
    echo-ok "$topic"
 | 
						|
 | 
						|
    local tpath="$DOTFILES/$topic"
 | 
						|
    local tsympath="$(topic-path $topic)"
 | 
						|
    if [[ -d "$topic/bin" ]]; then
 | 
						|
      export path=( $tpath/bin $path )
 | 
						|
      echo-ok "$topic: added $topic/bin to PATH, temporarily"
 | 
						|
    fi
 | 
						|
 | 
						|
    if [[ $LINK ]] || ! topic-enabled-p $topic; then
 | 
						|
      local op=install
 | 
						|
      OVERWRITE=1 mklink $tpath $tsympath
 | 
						|
      _topic-do $topic link
 | 
						|
    fi
 | 
						|
 | 
						|
    if [[ -z $INHIBIT_INIT ]]; then
 | 
						|
      _topic-do $topic ${FORCE_OP:-${op:-update}}
 | 
						|
      if [[ $op == install && $? != 0 ]]; then
 | 
						|
        topic-remove $topic
 | 
						|
      fi
 | 
						|
    fi
 | 
						|
  done
 | 
						|
}
 | 
						|
 | 
						|
function topic-remove {
 | 
						|
  for topic in $@; do
 | 
						|
    if [[ ! -d $topic ]]; then
 | 
						|
      echo-fail "$topic doesn't exist"
 | 
						|
      continue
 | 
						|
    elif ! topic-enabled-p $topic; then
 | 
						|
      echo-fail "$topic isn't enabled"
 | 
						|
      continue
 | 
						|
    fi
 | 
						|
 | 
						|
    [[ $INHIBIT_INIT ]] || _topic-do $topic clean
 | 
						|
    _exec rm -f "$(topic-path $topic)" && \
 | 
						|
      echo-ok "Removed $topic" || \
 | 
						|
      echo-fail "Couldn't remove $topic"
 | 
						|
  done
 | 
						|
}
 | 
						|
 | 
						|
function clean {
 | 
						|
  local paths=( $XDG_CACHE_HOME $XDG_CONFIG_HOME $XDG_DATA_HOME $XDG_BIN_HOME )
 | 
						|
  local links=( ~/.*(-@DN) ${^paths}/**(-@DN))
 | 
						|
  if [[ ${#links[@]} > 1 ]]; then
 | 
						|
    echo-info "Removing dead dotfile symlinks..."
 | 
						|
    for link in "${links[@]}"; do
 | 
						|
      [[ -e $link ]] || _exec rm -fv "$link"
 | 
						|
    done
 | 
						|
  else
 | 
						|
    echo-ok "No dead symlinks in HOME"
 | 
						|
  fi
 | 
						|
 | 
						|
  # Remove empty dotfile folders
 | 
						|
  local dirs=( ${^paths}/**(N/^F) ~/.*(N/^F) )
 | 
						|
  if [[ ${#dirs[@]} > 1 ]]; then
 | 
						|
    echo-info "Removing empty dotfile directories..."
 | 
						|
    for dir in "${dirs[@]}"; do
 | 
						|
      _exec rmdir "$dir" && echo-ok "Deleted ${dir/$HOME/~}"
 | 
						|
    done
 | 
						|
  else
 | 
						|
    echo-ok "No empty directories in HOME"
 | 
						|
  fi
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
## Bootstrap
 | 
						|
if [[ $ZSH_EVAL_CONTEXT != *:file ]]; then
 | 
						|
  while getopts acdehlLit opt; do
 | 
						|
    case $opt in
 | 
						|
      a) TARGETS="$(_topics)"  ;;
 | 
						|
      c) CLEAN=1               ;;
 | 
						|
      d) REMOVE=1              ;;
 | 
						|
      L) LIST=1                ;;
 | 
						|
      l) export LINK=1         ;&
 | 
						|
      i) export INHIBIT_INIT=1 ;;
 | 
						|
      t) export DRYRUN=1       ;;
 | 
						|
      h) cat <<EOL
 | 
						|
Usage: ${0:A:t} [-acdlLit] [TOPIC...]
 | 
						|
 | 
						|
  -a   Target all enabled topics (ignores TOPIC args)
 | 
						|
  -c   Afterwards, remove dead symlinks & empty dot-directories in $HOME.
 | 
						|
       Can be used alone.
 | 
						|
  -d   Disable & unlink topic(s)
 | 
						|
  -l   Only enable & relink topic(s) (implies -i)
 | 
						|
  -L   List enabled topics
 | 
						|
  -i   Do not run install/update/clean init scripts
 | 
						|
  -t   Do a test run; do not actually do anything
 | 
						|
EOL
 | 
						|
          exit ;;
 | 
						|
      *) >&2 echo-fail "Aborted."
 | 
						|
          exit 1 ;;
 | 
						|
    esac
 | 
						|
  done
 | 
						|
  shift $((OPTIND-1))
 | 
						|
 | 
						|
  if [[ $LIST ]]; then
 | 
						|
    _topics | tr ' ' $'\n'
 | 
						|
  else
 | 
						|
    pushd -qL "$DOTFILES"
 | 
						|
    if [[ $REMOVE ]]; then
 | 
						|
      topic-remove ${TARGETS:-$@}
 | 
						|
    else
 | 
						|
      topic-init ${TARGETS:-$@}
 | 
						|
    fi
 | 
						|
    [[ $CLEAN ]] && clean
 | 
						|
    popd -q
 | 
						|
  fi
 | 
						|
fi
 | 
						|
 | 
						|
# vim:set ft=sh:
 |