diff --git a/dotdrop/dotdrop.py b/dotdrop/dotdrop.py index e3cc6dd..48fc1b9 100644 --- a/dotdrop/dotdrop.py +++ b/dotdrop/dotdrop.py @@ -16,6 +16,7 @@ from dotdrop.options import Options from dotdrop.logger import Logger from dotdrop.templategen import Templategen from dotdrop.installer import Installer +from dotdrop.uninstaller import Uninstaller from dotdrop.updater import Updater from dotdrop.comparator import Comparator from dotdrop.importer import Importer @@ -621,14 +622,41 @@ def cmd_detail(opts): def cmd_uninstall(opts): - paths = opts.uninstall_path - iskey = opts.uninstall_iskey - prof = opts.conf.get_profile() + dotfiles = opts.dotfiles + keys = opts.uninstall_key - if not paths: - # is entire profile - # TODO - # TODO + if keys: + # update only specific keys for this profile + dotfiles = [] + for key in uniq_list(keys): + dotfile = opts.conf.get_dotfile(key) + if dotfile: + dotfiles.append(dotfile) + + if not dotfiles: + msg = f'no dotfile to uninstall for this profile (\"{opts.profile}\")' + LOG.warn(msg) + return False + + if opts.debug: + lfs = [k.key for k in dotfiles] + LOG.dbg(f'dotfiles registered for uninstall: {lfs}') + + uninst = Uninstaller(base=opts.dotpath, + workdir=opts.workdir, + dry=opts.dry, + debug=opts.debug, + backup_suffix=opts.install_backup_suffix) + uninstalled = 0 + for df in dotfiles: + res, msg = uninst.uninstall(df.src, + df.dst, + df.link) + if not res: + LOG.err(msg) + continue + uninstalled += 1 + LOG.log(f'\n{uninstalled} dotfile(s) uninstalled.') def cmd_remove(opts): diff --git a/dotdrop/options.py b/dotdrop/options.py index 3ae68f9..55f361e 100644 --- a/dotdrop/options.py +++ b/dotdrop/options.py @@ -68,7 +68,7 @@ Usage: dotdrop update [-VbfdkPz] [-c ] [-p ] [-w ] [-i ...] [...] dotdrop remove [-Vbfdk] [-c ] [-p ] [...] - dotdrop uninstall [-Vbfdk] [-c ] [-p ] [...] + dotdrop uninstall [-Vbfd] [-c ] [-p ] [...] dotdrop files [-VbTG] [-c ] [-p ] dotdrop detail [-Vb] [-c ] [-p ] [...] dotdrop profiles [-VbG] [-c ] @@ -343,8 +343,7 @@ class Options(AttrMonitor): def _apply_args_uninstall(self): """uninstall specifics""" - self.uninstall_path = self.args[''] - self.uninstall_iskey = self.args['--key'] + self.uninstall_key = self.args[''] def _apply_args_detail(self): """detail specifics""" @@ -361,6 +360,7 @@ class Options(AttrMonitor): self.cmd_update = self.args['update'] self.cmd_detail = self.args['detail'] self.cmd_remove = self.args['remove'] + self.cmd_uninstall = self.args['uninstall'] # adapt attributes based on arguments self.safe = not self.args['--force'] diff --git a/dotdrop/uninstaller.py b/dotdrop/uninstaller.py index 406d57a..0436661 100644 --- a/dotdrop/uninstaller.py +++ b/dotdrop/uninstaller.py @@ -22,6 +22,8 @@ class Uninstaller: @debug: enable debug @backup_suffix: suffix for dotfile backup file """ + # TODO dry + # TODO force base = os.path.expanduser(base) base = os.path.normpath(base) self.base = base @@ -60,27 +62,32 @@ class Uninstaller: msg = f'uninstalling \"{path}\" (link: {linktype})' self.log.dbg(msg) - self._remove(path) + ret, msg = self._remove(path) + if ret: + if not self.dry: + self.log.sub(f'uninstall {dst}') + return ret, msg def _remove(self, path): """remove path""" # TODO handle symlink backup = f'{path}{self.backup_suffix}' if os.path.exists(backup): + self.log.dbg(f'backup exists for {path}: {backup}') return self._replace(path, backup) try: removepath(path, self.log) except OSError as exc: err = f'removing \"{path}\" failed: {exc}' return False, err + return True, '' def _replace(self, path, backup): """replace path by backup""" - if os.path.isdir(path): - # TODO - return False, 'TODO' try: + self.log.dbg(f'mv {backup} {path}') os.replace(path, backup) except OSError as exc: err = f'replacing \"{path}\" by \"{backup}\" failed: {exc}' return False, err + return True, '' diff --git a/tests-ng/uninstall.sh b/tests-ng/uninstall.sh index c491fb1..147c7b3 100755 --- a/tests-ng/uninstall.sh +++ b/tests-ng/uninstall.sh @@ -25,6 +25,14 @@ echo -e "$(tput setaf 6)==> RUNNING $(basename "${BASH_SOURCE[0]}") <==$(tput sg ################################################################ # this is the test ################################################################ + +# $1 pattern +# $2 path +grep_or_fail() +{ + grep "${1}" "${2}" >/dev/null 2>&1 || (echo "pattern \"${1}\" not found in ${2}" && exit 1) +} + # dotdrop directory basedir=$(mktemp -d --suffix='-dotdrop-tests' || mktemp -d) mkdir -p "${basedir}"/dotfiles @@ -36,6 +44,8 @@ clear_on_exit "${basedir}" clear_on_exit "${tmpd}" echo "modified" > "${basedir}"/dotfiles/x +mkdir -p "${basedir}"/dotfiles/y +echo "modified" > "${basedir}"/dotfiles/y/file # create the config file cfg="${basedir}/config.yaml" @@ -48,10 +58,14 @@ dotfiles: f_x: src: x dst: ${tmpd}/x + d_y: + src: y + dst: ${tmpd}/y profiles: p1: dotfiles: - f_x + - d_y _EOF ######################### @@ -60,37 +74,53 @@ _EOF # install echo "[+] install (1)" -cd "${ddpath}" | ${bin} install -c "${cfg}" -f -p p1 --verbose | grep '^1 dotfile(s) installed.$' -[ "$?" != "0" ] && exit 1 +cd "${ddpath}" | ${bin} install -c "${cfg}" -f -p p1 --verbose | grep '^2 dotfile(s) installed.$' + +# tests [ ! -e "${tmpd}"/x ] && echo "f_x not installed" && exit 1 -grep 'modified' "${tmpd}"/x +[ ! -e "${tmpd}"/y/file ] && echo "d_y not installed" && exit 1 +grep_or_fail 'modified' "${tmpd}"/x +grep_or_fail 'modified' "${tmpd}"/y/file # uninstall -echo "[+] uninstall" +echo "[+] uninstall (1)" cd "${ddpath}" | ${bin} uninstall -c "${cfg}" -f -p p1 --verbose [ "$?" != "0" ] && exit 1 -[ -e "${tmpd}"/x ] && echo "f_x not installed" && exit 1 + +# tests +[ -e "${tmpd}"/x ] && echo "f_x not uninstalled" && exit 1 +[ -d "${tmpd}"/y ] && echo "d_y not uninstalled" && exit 1 +[ -e "${tmpd}"/y/file ] && echo "d_y file not uninstalled" && exit 1 ######################### ## with original ######################### echo 'original' > "${tmpd}"/x +mkdir -p "${tmpd}"/y +echo "original" > "${tmpd}"/y/file # install echo "[+] install (2)" -cd "${ddpath}" | ${bin} install -c "${cfg}" -f -p p1 --verbose | grep '^1 dotfile(s) installed.$' -[ "$?" != "0" ] && exit 1 +cd "${ddpath}" | ${bin} install -c "${cfg}" -f -p p1 --verbose | grep '^2 dotfile(s) installed.$' + +# tests [ ! -e "${tmpd}"/x ] && echo "f_x not installed" && exit 1 -grep 'modified' "${tmpd}"/x +[ ! -d "${tmpd}"/y ] && echo "d_y not installed" && exit 1 +[ ! -e "${tmpd}"/y/file ] && echo "d_y file not installed" && exit 1 +grep_or_fail 'modified' "${tmpd}"/x +grep_or_fail 'modified' "${tmpd}"/y/file # uninstall -echo "[+] uninstall" +echo "[+] uninstall (2)" cd "${ddpath}" | ${bin} uninstall -c "${cfg}" -f -p p1 --verbose [ "$?" != "0" ] && exit 1 -[ -e "${tmpd}"/x ] && echo "f_x not installed" && exit 1 -grep 'original' "${tmpd}"/x -# TODO handle directory +# tests +[ ! -e "${tmpd}"/x ] && echo "f_x backup not restored" && exit 1 +[ ! -d "${tmpd}"/y ] && echo "d_y backup not restored" && exit 1 +[ ! -e "${tmpd}"/y/file ] && echo "d_y backup not restored" && exit 1 +grep_or_fail 'original' "${tmpd}"/x +grep_or_fail 'original' "${tmpd}"/y/file echo "OK" exit 0