diff --git a/README.md b/README.md index 0adaf09..ba91b6e 100644 --- a/README.md +++ b/README.md @@ -513,9 +513,10 @@ the following entries: * `longkey`: use long keys for dotfiles when importing (default *false*) * `keepdot`: preserve leading dot when importing hidden file in the `dotpath` (default *false*) * `link_by_default`: when importing a dotfile set `link` to that value per default (default *false*) + * `workdir`: directory where templates are installed before being symlink when using `link` (default *~/.config/dotdrop*) * **dotfiles** entry: a list of dotfiles - * When `link` is true, dotdrop will create a symlink instead of copying. Template generation (as in [template](#template)) is not supported when `link` is true (default *false*). + * When `link` is true, dotdrop will create a symlink instead of copying (default *false*). * `actions` contains a list of action keys that need to be defined in the **actions** entry below. * `trans` contains a list of transformation keys that need to be defined in the **trans** entry below. ``` diff --git a/dotdrop/config.py b/dotdrop/config.py index 6ab8870..9516f38 100644 --- a/dotdrop/config.py +++ b/dotdrop/config.py @@ -29,6 +29,7 @@ class Cfg: key_long = 'longkey' key_keepdot = 'keepdot' key_deflink = 'link_by_default' + key_workdir = 'workdir' # actions keys key_actions = 'actions' @@ -62,6 +63,7 @@ class Cfg: default_longkey = False default_keepdot = False default_link_by_default = False + default_workdir = '~/.config/dotdrop' def __init__(self, cfgpath): if not os.path.exists(cfgpath): @@ -295,6 +297,8 @@ class Cfg: self.lnk_settings[self.key_keepdot] = self.default_keepdot if self.key_deflink not in self.lnk_settings: self.lnk_settings[self.key_deflink] = self.default_link_by_default + if self.key_workdir not in self.lnk_settings: + self.lnk_settings[self.key_workdir] = self.default_workdir def abs_dotpath(self, path): """transform path to an absolute path based on config path""" diff --git a/dotdrop/dotdrop.py b/dotdrop/dotdrop.py index 9458ef0..16b620d 100644 --- a/dotdrop/dotdrop.py +++ b/dotdrop/dotdrop.py @@ -85,7 +85,8 @@ def install(opts, conf): variables=opts['variables'], debug=opts['debug']) inst = Installer(create=opts['create'], backup=opts['backup'], dry=opts['dry'], safe=opts['safe'], base=opts['dotpath'], - diff=opts['installdiff'], debug=opts['debug']) + workdir=opts['workdir'], diff=opts['installdiff'], + debug=opts['debug']) installed = [] for dotfile in dotfiles: if dotfile.actions and Cfg.key_actions_pre in dotfile.actions: @@ -99,7 +100,7 @@ def install(opts, conf): if opts['debug']: LOG.dbg('installing {}'.format(dotfile)) if hasattr(dotfile, 'link') and dotfile.link: - r = inst.link(dotfile.src, dotfile.dst) + r = inst.link(t, dotfile.src, dotfile.dst) else: src = dotfile.src tmp = None @@ -189,7 +190,7 @@ def compare(opts, conf, tmp, focus=None, ignore=[]): variables=opts['variables'], debug=opts['debug']) inst = Installer(create=opts['create'], backup=opts['backup'], dry=opts['dry'], base=opts['dotpath'], - debug=opts['debug']) + workdir=opts['workdir'], debug=opts['debug']) comp = Comparator(diffopts=opts['dopts'], debug=opts['debug'], ignore=ignore) diff --git a/dotdrop/installer.py b/dotdrop/installer.py index b6528f1..8557464 100644 --- a/dotdrop/installer.py +++ b/dotdrop/installer.py @@ -17,28 +17,25 @@ import dotdrop.utils as utils class Installer: BACKUP_SUFFIX = '.dotdropbak' - # TODO get this from the config file with a default - DOTDROP_WORK = '{}/.config/dotdrop'.format(os.path.expanduser('~')) def __init__(self, base='.', create=True, backup=True, - dry=False, safe=False, debug=False, diff=True): + dry=False, safe=False, workdir='~/.config/dotdrop', + debug=False, diff=True): self.create = create self.backup = backup self.dry = dry self.safe = safe + self.workdir = os.path.expanduser(workdir) self.base = base self.debug = debug self.diff = diff self.comparing = False self.log = Logger() - def _dotdrop_work(self): - os.makedirs(self.DOTDROP_WORK, exist_ok=True) - def install(self, templater, src, dst): """install the src to dst using a template""" src = os.path.join(self.base, os.path.expanduser(src)) - dst = os.path.join(self.base, os.path.expanduser(dst)) + dst = os.path.expanduser(dst) if utils.samefile(src, dst): # symlink loop self.log.err('dotfile points to itself: {}'.format(dst)) @@ -49,22 +46,19 @@ class Installer: return self._handle_dir(templater, src, dst) return self._handle_file(templater, src, dst) - def link(self, src, dst): + def link(self, templater, src, dst): """set src as the link target of dst""" src = os.path.join(self.base, os.path.expanduser(src)) - dst = os.path.join(self.base, os.path.expanduser(dst)) + dst = os.path.expanduser(dst) if Templategen.is_template(src): - # TODO - # first make sure the template is generated in the working dir - - # if it's not generate it - - # and then symlink it as any other file - # by udating src and dst - return [] - - # is not a template + if self.debug: + self.log.dbg('dotfile is a template') + self.log.dbg('install to {} and symlink'.format(self.workdir)) + tmp = self._pivot_path(dst, self.workdir, striphome=True) + if not self.install(templater, src, tmp): + return [] + src = tmp return self._link(src, dst) def _link(self, src, dst): @@ -95,7 +89,7 @@ class Installer: self.log.err('creating directory for \"{}\"'.format(dst)) return [] os.symlink(src, dst) - self.log.sub('linked {} to {}'.format(dst, src)) + self.log.sub('linked \"{}\" to \"{}\"'.format(dst, src)) return [(src, dst)] def _handle_file(self, templater, src, dst): @@ -206,19 +200,24 @@ class Installer: self.log.log('backup {} to {}'.format(path, dst)) os.rename(path, dst) + def _pivot_path(self, path, newdir, striphome=False): + """change path to be under newdir""" + if striphome: + home = os.path.expanduser('~') + path = path.lstrip(home) + sub = path.lstrip(os.sep) + return os.path.join(newdir, sub) + def _install_to_temp(self, templater, src, dst, tmpdir): - """install a dotfile to a tempdir for comparing""" - sub = dst - if dst[0] == os.sep: - sub = dst[1:] - tmpdst = os.path.join(tmpdir, sub) + """install a dotfile to a tempdir""" + tmpdst = self._pivot_path(dst, tmpdir) return self.install(templater, src, tmpdst), tmpdst def install_to_temp(self, templater, tmpdir, src, dst): - """compare a temporary generated dotfile with the local one""" + """install a dotfile to a tempdir""" ret = False tmpdst = '' - # saved some flags while comparing + # save some flags while comparing self.comparing = True drysaved = self.dry self.dry = False diff --git a/tests-ng/link-templates.sh b/tests-ng/link-templates.sh new file mode 100755 index 0000000..36e3e2f --- /dev/null +++ b/tests-ng/link-templates.sh @@ -0,0 +1,96 @@ +#!/usr/bin/env bash +# author: deadc0de6 (https://github.com/deadc0de6) +# Copyright (c) 2017, deadc0de6 +# +# test link of templates +# returns 1 in case of error +# + +# 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" + +echo "dotdrop path: ${ddpath}" +echo "pythonpath: ${PYTHONPATH}" + +# get the helpers +source ${cur}/helpers + +echo "RUNNING $(basename $BASH_SOURCE)" + +################################################################ +# this is the test +################################################################ + +# the dotfile source +tmps=`mktemp -d` +mkdir -p ${tmps}/dotfiles +echo "dotfiles source (dotpath): ${tmps}" +# the dotfile destination +tmpd=`mktemp -d` +echo "dotfiles destination: ${tmpd}" +# the workdir +tmpw=`mktemp -d` +echo "workdir: ${tmpw}" + + +# create the config file +cfg="${tmps}/config.yaml" + +cat > ${cfg} << _EOF +config: + backup: true + create: true + dotpath: dotfiles + workdir: ${tmpw} +dotfiles: + f_abc: + dst: ${tmpd}/abc + src: abc + link: true +profiles: + p1: + dotfiles: + - f_abc +_EOF +cat ${cfg} + +# create the dotfile +echo "{{@@ profile @@}}" > ${tmps}/dotfiles/abc +echo "blabla" >> ${tmps}/dotfiles/abc + +# install +cd ${ddpath} | ${bin} install -f -c ${cfg} -p p1 -b -V + +# checks +[ ! -e ${tmpd}/abc ] && echo "[ERROR] dotfile not installed" && exit 1 +[ ! -h ${tmpd}/abc ] && echo "[ERROR] dotfile is not a symlink" && exit 1 + +## CLEANING +rm -rf ${tmps} ${tmpd} ${tmpw} + +echo "OK" +exit 0 diff --git a/tests-ng/variables.sh b/tests-ng/variables.sh index 2ef3c6b..8b6f7b4 100755 --- a/tests-ng/variables.sh +++ b/tests-ng/variables.sh @@ -89,7 +89,7 @@ grep '^this is some test' ${tmpd}/abc >/dev/null grep '^12' ${tmpd}/abc >/dev/null grep '^another test' ${tmpd}/abc >/dev/null -cat ${tmpd}/abc +#cat ${tmpd}/abc ## CLEANING rm -rf ${tmps} ${tmpd} diff --git a/tests/helpers.py b/tests/helpers.py index b218c7c..293777d 100644 --- a/tests/helpers.py +++ b/tests/helpers.py @@ -98,11 +98,13 @@ def create_fake_config(directory, configname='config.yaml', dotpath='dotfiles', backup=True, create=True): '''Create a fake config file''' path = os.path.join(directory, configname) + workdir = os.path.join(directory, 'workdir') with open(path, 'w') as f: f.write('config:\n') f.write(' backup: {}\n'.format(str(backup))) f.write(' create: {}\n'.format(str(create))) f.write(' dotpath: {}\n'.format(dotpath)) + f.write(' workdir: {}\n'.format(workdir)) f.write('dotfiles:\n') f.write('profiles:\n') f.write('actions:\n')