From a547f0f72bcbceb939a32ccf00c936c095a27e51 Mon Sep 17 00:00:00 2001 From: deadc0de6 Date: Sat, 28 May 2022 23:31:20 +0200 Subject: [PATCH] adding relative symlink for #348 --- dotdrop/cfg_yaml.py | 11 +++- dotdrop/dotdrop.py | 17 ++---- dotdrop/installer.py | 82 ++++++++++++++++++++++----- dotdrop/linktypes.py | 2 + tests-ng/symlink-relative.sh | 107 +++++++++++++++++++++++++++++++++++ 5 files changed, 191 insertions(+), 28 deletions(-) create mode 100755 tests-ng/symlink-relative.sh diff --git a/dotdrop/cfg_yaml.py b/dotdrop/cfg_yaml.py index 25a9e07..1058cca 100644 --- a/dotdrop/cfg_yaml.py +++ b/dotdrop/cfg_yaml.py @@ -97,9 +97,12 @@ class CfgYaml: lnk_nolink = LinkTypes.NOLINK.name.lower() lnk_link = LinkTypes.LINK.name.lower() lnk_children = LinkTypes.LINK_CHILDREN.name.lower() + lnk_absolute = LinkTypes.ABSOLUTE.name.lower() + lnk_relative = LinkTypes.RELATIVE.name.lower() # checks - allowed_link_val = [lnk_nolink, lnk_link, lnk_children] + allowed_link_val = [lnk_nolink, lnk_link, lnk_children, + lnk_absolute, lnk_relative] top_entries = [key_dotfiles, key_settings, key_profiles] def __init__(self, path, profile=None, addprofiles=None, @@ -297,8 +300,9 @@ class CfgYaml: newlink = self._template_item(link) # check link value if newlink not in self.allowed_link_val: - err = 'bad value: {}'.format(newlink) + err = 'bad link value: {}'.format(newlink) self._log.err(err) + self._log.err('allowed: {}'.format(self.allowed_link_val)) raise YamlException('config content error: {}'.format(err)) return newlink @@ -1216,8 +1220,9 @@ class CfgYaml: return val = settings[self.key_settings_link_dotfile_default] if val not in self.allowed_link_val: - err = 'bad value: {}'.format(val) + err = 'bad link value: {}'.format(val) self._log.err(err) + self._log.err('allowed: {}'.format(self.allowed_link_val)) raise YamlException('config content error: {}'.format(err)) @classmethod diff --git a/dotdrop/dotdrop.py b/dotdrop/dotdrop.py index 69a0af9..7d636d8 100644 --- a/dotdrop/dotdrop.py +++ b/dotdrop/dotdrop.py @@ -220,8 +220,11 @@ def _dotfile_install(opts, dotfile, tmpdir=None): dotfile.src, ignore=ignores, ) - if hasattr(dotfile, 'link') and dotfile.link == LinkTypes.LINK: - # link + if hasattr(dotfile, 'link') and dotfile.link in ( + LinkTypes.LINK, LinkTypes.LINK_CHILDREN, + LinkTypes.RELATIVE, LinkTypes.ABSOLUTE + ): + # link|relative|absolute|link_children ret, err = inst.install(templ, dotfile.src, dotfile.dst, dotfile.link, actionexec=pre_actions_exec, @@ -229,16 +232,6 @@ def _dotfile_install(opts, dotfile, tmpdir=None): ignore=ignores, chmod=dotfile.chmod, force_chmod=opts.install_force_chmod) - elif hasattr(dotfile, 'link') and \ - dotfile.link == LinkTypes.LINK_CHILDREN: - # link_children - ret, err = inst.install(templ, dotfile.src, dotfile.dst, - dotfile.link, - actionexec=pre_actions_exec, - is_template=is_template, - chmod=dotfile.chmod, - ignore=ignores, - force_chmod=opts.install_force_chmod) else: # nolink src = dotfile.src diff --git a/dotdrop/installer.py b/dotdrop/installer.py index db05773..1c508c1 100644 --- a/dotdrop/installer.py +++ b/dotdrop/installer.py @@ -133,12 +133,18 @@ class Installer: actionexec=actionexec, noempty=noempty, ignore=ignore, is_template=is_template) - elif linktype == LinkTypes.LINK: + elif linktype in (LinkTypes.LINK, LinkTypes.ABSOLUTE): # symlink - ret, err = self._link(templater, src, dst, - actionexec=actionexec, - is_template=is_template, - ignore=ignore) + ret, err = self._link_absolute(templater, src, dst, + actionexec=actionexec, + is_template=is_template, + ignore=ignore) + elif linktype == LinkTypes.RELATIVE: + # symlink + ret, err = self._link_relative(templater, src, dst, + actionexec=actionexec, + is_template=is_template, + ignore=ignore) elif linktype == LinkTypes.LINK_CHILDREN: # symlink direct children if not isdir: @@ -246,10 +252,48 @@ class Installer: # low level accessors for public methods ######################################################## - def _link(self, templater, src, dst, actionexec=None, - is_template=True, ignore=None): + def _link_absolute(self, templater, src, dst, + actionexec=None, + is_template=True, + ignore=None): """ - install link:link + install link:absolute|link + + return + - True, None : success + - False, error_msg : error + - False, None : ignored + - False, 'aborted' : user aborted + """ + return self._link_dotfile(templater, src, dst, + actionexec=actionexec, + is_template=is_template, + ignore=ignore, + absolute=True) + + def _link_relative(self, templater, src, dst, + actionexec=None, + is_template=True, + ignore=None): + """ + install link:relative + + return + - True, None : success + - False, error_msg : error + - False, None : ignored + - False, 'aborted' : user aborted + """ + return self._link_dotfile(templater, src, dst, + actionexec=actionexec, + is_template=is_template, + ignore=ignore, + absolute=False) + + def _link_dotfile(self, templater, src, dst, actionexec=None, + is_template=True, ignore=None, absolute=True): + """ + symlink return - True, None : success @@ -270,7 +314,8 @@ class Installer: if not ret and not os.path.exists(tmp): return ret, err src = tmp - ret, err = self._symlink(src, dst, actionexec=actionexec) + ret, err = self._symlink(src, dst, actionexec=actionexec, + absolute=absolute) return ret, err def _link_children(self, templater, src, dst, @@ -354,7 +399,7 @@ class Installer: # file operations ######################################################## - def _symlink(self, src, dst, actionexec=None): + def _symlink(self, src, dst, actionexec=None, absolute=True): """ set src as a link target of dst @@ -415,9 +460,20 @@ class Installer: return False, err # create symlink - os.symlink(src, dst) - if not self.comparing: - self.log.sub('linked {} to {}'.format(dst, src)) + if absolute: + # absolute symlink pointing to src named dst + os.symlink(src, dst) + if not self.comparing: + self.log.sub('linked {} to {}'.format(dst, src)) + else: + # relative symlink + dstrel = dst + if not os.path.isdir(dstrel): + dstrel = os.path.dirname(dstrel) + rel = os.path.relpath(src, dstrel) + os.symlink(rel, dst) + if not self.comparing: + self.log.sub('linked {} to {}'.format(dst, rel)) return True, None def _copy_file(self, templater, src, dst, diff --git a/dotdrop/linktypes.py b/dotdrop/linktypes.py index e3288ad..9e15c0e 100644 --- a/dotdrop/linktypes.py +++ b/dotdrop/linktypes.py @@ -16,6 +16,8 @@ class LinkTypes(IntEnum): NOLINK = 0 LINK = 1 LINK_CHILDREN = 2 + ABSOLUTE = 3 + RELATIVE = 4 @classmethod def get(cls, key, default=None): diff --git a/tests-ng/symlink-relative.sh b/tests-ng/symlink-relative.sh new file mode 100755 index 0000000..fd0d9f2 --- /dev/null +++ b/tests-ng/symlink-relative.sh @@ -0,0 +1,107 @@ +#!/usr/bin/env bash +# author: deadc0de6 (https://github.com/deadc0de6) +# Copyright (c) 2022, deadc0de6 +# +# test relative symlink +# + +# exit on first error +set -e + +# all this crap to get current path +rl="readlink -f" +if ! ${rl} "${0}" >/dev/null 2>&1; then + rl="realpath" + + if ! hash ${rl}; then + echo "\"${rl}\" not found !" && exit 1 + fi +fi +cur=$(dirname "$(${rl} "${0}")") + +#hash dotdrop >/dev/null 2>&1 +#[ "$?" != "0" ] && echo "install dotdrop to run tests" && exit 1 + +#echo "called with ${1}" + +# dotdrop path can be pass as argument +ddpath="${cur}/../" +[ "${1}" != "" ] && ddpath="${1}" +[ ! -d ${ddpath} ] && echo "ddpath \"${ddpath}\" is not a directory" && exit 1 + +export PYTHONPATH="${ddpath}:${PYTHONPATH}" +bin="python3 -m dotdrop.dotdrop" +hash coverage 2>/dev/null && bin="coverage run -a --source=dotdrop -m dotdrop.dotdrop" || true + +echo "dotdrop path: ${ddpath}" +echo "pythonpath: ${PYTHONPATH}" + +# get the helpers +source ${cur}/helpers + +echo -e "$(tput setaf 6)==> RUNNING $(basename $BASH_SOURCE) <==$(tput sgr0)" + +################################################################ +# this is the test +################################################################ + +# the dotfile source +tmps=`mktemp -d --suffix='-dotdrop-tests' || mktemp -d` +mkdir -p ${tmps}/dotfiles +# the dotfile destination +tmpd=`mktemp -d --suffix='-dotdrop-tests' || mktemp -d` +#echo "dotfile destination: ${tmpd}" + +clear_on_exit "${tmps}" +clear_on_exit "${tmpd}" + +################################################## +# test symlink directory +################################################## +# create the dotfile +echo "file1" > ${tmps}/dotfiles/abc + +# create the config file +cfg="${tmps}/config.yaml" +cat > ${cfg} << _EOF +config: + backup: true + create: true + dotpath: dotfiles + link_dotfile_default: nolink +dotfiles: + d_abc: + dst: ${tmpd}/abc + src: abc + link: relative + d_abc2: + dst: ${tmpd}/abc2 + src: abc + link: absolute +profiles: + p1: + dotfiles: + - d_abc + - d_abc2 +_EOF +#cat ${cfg} + +# install +cd ${ddpath} | ${bin} install -f -c ${cfg} -p p1 -V + +# ensure exists and is link +[ ! -h ${tmpd}/abc ] && echo "not a symlink" && exit 1 +[ ! -h ${tmpd}/abc2 ] && echo "not a symlink" && exit 1 + +ls -l ${tmpd}/abc | grep '..' || exit 1 +ls -l ${tmpd}/abc2 + +grep 'file1' ${tmpd}/abc +grep 'file1' ${tmpd}/abc2 + +## TODO with templates +echo "TODO with templates" +exit 1 + +echo "OK" +exit 0