From b157a5c4ca72c0e85981457ac8bf7b7b39309d2c Mon Sep 17 00:00:00 2001 From: deadc0de6 Date: Tue, 12 Sep 2023 22:49:03 +0200 Subject: [PATCH] uninstall --- dotdrop/uninstaller.py | 86 +++++++++++++++++++++++++++++ tests-ng/uninstall-symlink.sh | 100 ++++++++++++++++++++++++++++++++++ tests-ng/uninstall.sh | 96 ++++++++++++++++++++++++++++++++ 3 files changed, 282 insertions(+) create mode 100644 dotdrop/uninstaller.py create mode 100755 tests-ng/uninstall-symlink.sh create mode 100755 tests-ng/uninstall.sh diff --git a/dotdrop/uninstaller.py b/dotdrop/uninstaller.py new file mode 100644 index 0000000..406d57a --- /dev/null +++ b/dotdrop/uninstaller.py @@ -0,0 +1,86 @@ +""" +author: deadc0de6 (https://github.com/deadc0de6) +Copyright (c) 2023, deadc0de6 + +handle the un-installation of dotfiles +""" + +import os +from dotdrop.logger import Logger +from dotdrop.utils import removepath + + +class Uninstaller: + """dotfile uninstaller""" + + def __init__(self, base='.', workdir='~/.config/dotdrop', + dry=False, debug=False, backup_suffix='.dotdropbak'): + """ + @base: directory path where to search for templates + @workdir: where to install template before symlinking + @dry: just simulate + @debug: enable debug + @backup_suffix: suffix for dotfile backup file + """ + base = os.path.expanduser(base) + base = os.path.normpath(base) + self.base = base + workdir = os.path.expanduser(workdir) + workdir = os.path.normpath(workdir) + self.workdir = workdir + self.dry = dry + self.debug = debug + self.backup_suffix = backup_suffix + + self.log = Logger(debug=self.debug) + + def uninstall(self, src, dst, linktype): + """ + uninstall dst + @src: dotfile source path in dotpath + @dst: dotfile destination path in the FS + @linktype: linktypes.LinkTypes + + return + - True, None : success + - False, error_msg : error + """ + if not src or not dst: + self.log.dbg(f'cannot uninstall empty {src} or {dst}') + return True, None + + # ensure exists + path = os.path.expanduser(dst) + path = os.path.normpath(path) + path = path.rstrip(os.sep) + + if not os.path.exists(path): + self.log.dbg(f'cannot uninstall non existing {path}') + return True, None + + msg = f'uninstalling \"{path}\" (link: {linktype})' + self.log.dbg(msg) + self._remove(path) + + def _remove(self, path): + """remove path""" + # TODO handle symlink + backup = f'{path}{self.backup_suffix}' + if os.path.exists(backup): + return self._replace(path, backup) + try: + removepath(path, self.log) + except OSError as exc: + err = f'removing \"{path}\" failed: {exc}' + return False, err + + def _replace(self, path, backup): + """replace path by backup""" + if os.path.isdir(path): + # TODO + return False, 'TODO' + try: + os.replace(path, backup) + except OSError as exc: + err = f'replacing \"{path}\" by \"{backup}\" failed: {exc}' + return False, err diff --git a/tests-ng/uninstall-symlink.sh b/tests-ng/uninstall-symlink.sh new file mode 100755 index 0000000..2a50ed8 --- /dev/null +++ b/tests-ng/uninstall-symlink.sh @@ -0,0 +1,100 @@ +#!/usr/bin/env bash +# author: deadc0de6 (https://github.com/deadc0de6) +# Copyright (c) 2023, deadc0de6 +# +# test uninstall with symlink +# returns 1 in case of error +# + +## start-cookie +set -e +cur=$(cd "$(dirname "${0}")" && pwd) +ddpath="${cur}/../" +export PYTHONPATH="${ddpath}:${PYTHONPATH}" +altbin="python3 -m dotdrop.dotdrop" +if hash coverage 2>/dev/null; then + mkdir -p coverages/ + altbin="coverage run -p --data-file coverages/coverage --source=dotdrop -m dotdrop.dotdrop" +fi +bin="${DT_BIN:-${altbin}}" +# shellcheck source=tests-ng/helpers +source "${cur}"/helpers +echo -e "$(tput setaf 6)==> RUNNING $(basename "${BASH_SOURCE[0]}") <==$(tput sgr0)" +## end-cookie + +################################################################ +# this is the test +################################################################ +# dotdrop directory +basedir=$(mktemp -d --suffix='-dotdrop-tests' || mktemp -d) +mkdir -p "${basedir}"/dotfiles +echo "[+] dotdrop dir: ${basedir}" +echo "[+] dotpath dir: ${basedir}/dotfiles" +tmpd=$(mktemp -d --suffix='-dotdrop-tests' || mktemp -d) + +clear_on_exit "${basedir}" +clear_on_exit "${tmpd}" + +# TODO handle +# - symlink file (absolute, relative) +# - symlink directory (absolute, relative, link_children) +# - transformation +# - template + +echo "modified" > "${basedir}"/dotfiles/x + +# create the config file +cfg="${basedir}/config.yaml" +cat > "${cfg}" << _EOF +config: + backup: true + create: true + dotpath: dotfiles +dotfiles: + f_x: + src: x + dst: ${tmpd}/x +profiles: + p1: + dotfiles: + - f_x +_EOF + +######################### +## no original +######################### + +# install +echo "[+] install (1)" +cd "${ddpath}" | ${bin} install -c "${cfg}" -f -p p1 --verbose | grep '^1 dotfile(s) installed.$' +[ "$?" != "0" ] && exit 1 +[ ! -e "${tmpd}"/x ] && echo "f_x not installed" && exit 1 +grep 'modified' "${tmpd}"/x + +# uninstall +echo "[+] uninstall" +cd "${ddpath}" | ${bin} uninstall -c "${cfg}" -f -p p1 --verbose +[ "$?" != "0" ] && exit 1 +[ -e "${tmpd}"/x ] && echo "f_x not installed" && exit 1 + +######################### +## with original +######################### +echo 'original' > "${tmpd}"/x + +# install +echo "[+] install (2)" +cd "${ddpath}" | ${bin} install -c "${cfg}" -f -p p1 --verbose | grep '^1 dotfile(s) installed.$' +[ "$?" != "0" ] && exit 1 +[ ! -e "${tmpd}"/x ] && echo "f_x not installed" && exit 1 +grep 'modified' "${tmpd}"/x + +# uninstall +echo "[+] uninstall" +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 + +echo "OK" +exit 0 diff --git a/tests-ng/uninstall.sh b/tests-ng/uninstall.sh new file mode 100755 index 0000000..c491fb1 --- /dev/null +++ b/tests-ng/uninstall.sh @@ -0,0 +1,96 @@ +#!/usr/bin/env bash +# author: deadc0de6 (https://github.com/deadc0de6) +# Copyright (c) 2023, deadc0de6 +# +# test uninstall (no symlink) +# returns 1 in case of error +# + +## start-cookie +set -e +cur=$(cd "$(dirname "${0}")" && pwd) +ddpath="${cur}/../" +export PYTHONPATH="${ddpath}:${PYTHONPATH}" +altbin="python3 -m dotdrop.dotdrop" +if hash coverage 2>/dev/null; then + mkdir -p coverages/ + altbin="coverage run -p --data-file coverages/coverage --source=dotdrop -m dotdrop.dotdrop" +fi +bin="${DT_BIN:-${altbin}}" +# shellcheck source=tests-ng/helpers +source "${cur}"/helpers +echo -e "$(tput setaf 6)==> RUNNING $(basename "${BASH_SOURCE[0]}") <==$(tput sgr0)" +## end-cookie + +################################################################ +# this is the test +################################################################ +# dotdrop directory +basedir=$(mktemp -d --suffix='-dotdrop-tests' || mktemp -d) +mkdir -p "${basedir}"/dotfiles +echo "[+] dotdrop dir: ${basedir}" +echo "[+] dotpath dir: ${basedir}/dotfiles" +tmpd=$(mktemp -d --suffix='-dotdrop-tests' || mktemp -d) + +clear_on_exit "${basedir}" +clear_on_exit "${tmpd}" + +echo "modified" > "${basedir}"/dotfiles/x + +# create the config file +cfg="${basedir}/config.yaml" +cat > "${cfg}" << _EOF +config: + backup: true + create: true + dotpath: dotfiles +dotfiles: + f_x: + src: x + dst: ${tmpd}/x +profiles: + p1: + dotfiles: + - f_x +_EOF + +######################### +## no original +######################### + +# install +echo "[+] install (1)" +cd "${ddpath}" | ${bin} install -c "${cfg}" -f -p p1 --verbose | grep '^1 dotfile(s) installed.$' +[ "$?" != "0" ] && exit 1 +[ ! -e "${tmpd}"/x ] && echo "f_x not installed" && exit 1 +grep 'modified' "${tmpd}"/x + +# uninstall +echo "[+] uninstall" +cd "${ddpath}" | ${bin} uninstall -c "${cfg}" -f -p p1 --verbose +[ "$?" != "0" ] && exit 1 +[ -e "${tmpd}"/x ] && echo "f_x not installed" && exit 1 + +######################### +## with original +######################### +echo 'original' > "${tmpd}"/x + +# install +echo "[+] install (2)" +cd "${ddpath}" | ${bin} install -c "${cfg}" -f -p p1 --verbose | grep '^1 dotfile(s) installed.$' +[ "$?" != "0" ] && exit 1 +[ ! -e "${tmpd}"/x ] && echo "f_x not installed" && exit 1 +grep 'modified' "${tmpd}"/x + +# uninstall +echo "[+] uninstall" +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 + +echo "OK" +exit 0