#!/usr/bin/env zsh # Dotfile manager # # By Henrik Lissner # 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="-svf" 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 [[ -n $LINK ]] || ! topic-enabled-p $topic; then local op=install 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 [[ -z $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 <&2 echo-fail "Aborted." exit 1 ;; esac done shift $((OPTIND-1)) if [[ -n $LIST ]]; then _topics | tr ' ' $'\n' else pushd -qL "$DOTFILES" if [[ -n $REMOVE ]]; then topic-remove ${TARGETS:-$@} else topic-init ${TARGETS:-$@} fi [[ -n $CLEAN ]] && clean popd -q fi fi # vim:set ft=sh: