From d9b3094294dab5053e2bb3608f50cdc37b2c90c5 Mon Sep 17 00:00:00 2001 From: deadc0de6 Date: Thu, 8 Oct 2020 19:41:50 +0200 Subject: [PATCH 01/13] adding test for notemplate --- tests-ng/notemplate.sh | 125 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 125 insertions(+) create mode 100755 tests-ng/notemplate.sh diff --git a/tests-ng/notemplate.sh b/tests-ng/notemplate.sh new file mode 100755 index 0000000..6e02b1a --- /dev/null +++ b/tests-ng/notemplate.sh @@ -0,0 +1,125 @@ +#!/usr/bin/env bash +# author: deadc0de6 (https://github.com/deadc0de6) +# Copyright (c) 2020, deadc0de6 +# +# test notemplate +# 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" +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 +#echo "dotfile source: ${tmps}" +# the dotfile destination +tmpd=`mktemp -d --suffix='-dotdrop-tests' || mktemp -d` +#echo "dotfile destination: ${tmpd}" + +# create the config file +cfg="${tmps}/config.yaml" + +# globally +cat > ${cfg} << _EOF +config: + backup: true + create: true + dotpath: dotfiles + template_dotfile_default: true +dotfiles: + d_d1: + dst: ${tmpd}/d1 + src: d1 +profiles: + p1: + dotfiles: + - d_d1 +_EOF +#cat ${cfg} + +# create the dotfile +echo "before" > ${tmps}/dotfiles/d1 +echo "{#@@ should not be stripped @@#}" >> ${tmps}/dotfiles/d1 +echo "{{@@ header() @@}}" >> ${tmps}/dotfiles/d1 +echo "after" >> ${tmps}/dotfiles/d1 + +# install +cd ${ddpath} | ${bin} install -f -c ${cfg} -p p1 -V + +# test existence +[ ! -e ${tmpd}/d1 ] && echo 'not installed' && exit 1 +grep 'header' ${tmpd}/d1 || echo "header stripped" && exit 1 +grep 'should not be stripped' ${tmpd}/d1 || echo "comment stripped" && exit 1 + +# through the dotfile +cat > ${cfg} << _EOF +config: + backup: true + create: true + dotpath: dotfiles +dotfiles: + d_d1: + dst: ${tmpd}/d1 + src: d1 + notemplate: true +profiles: + p1: + dotfiles: + - d_d1 +_EOF +#cat ${cfg} + +# clean destination +rm -rf ${tmpd}/* + +# install +cd ${ddpath} | ${bin} install -f -c ${cfg} -p p1 -V + +# test existence +[ ! -e ${tmpd}/d1 ] && echo 'not installed' && exit 1 +grep 'header' ${tmpd}/d1 || echo "header stripped" && exit 1 +grep 'should not be stripped' ${tmpd}/d1 || echo "comment stripped" && exit 1 + +## CLEANING +rm -rf ${tmps} ${tmpd} + +echo "OK" +exit 0 From a57496a9ba044cb3f5c45392b61311de91a3fd32 Mon Sep 17 00:00:00 2001 From: deadc0de6 Date: Thu, 8 Oct 2020 19:42:04 +0200 Subject: [PATCH 02/13] adding notemplate config options --- dotdrop/cfg_yaml.py | 6 ++++++ dotdrop/dotfile.py | 8 +++++++- dotdrop/settings.py | 6 +++++- 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/dotdrop/cfg_yaml.py b/dotdrop/cfg_yaml.py index 11bdaad..bd76ace 100644 --- a/dotdrop/cfg_yaml.py +++ b/dotdrop/cfg_yaml.py @@ -58,6 +58,7 @@ class CfgYaml: key_dotfile_actions = 'actions' key_dotfile_link_children = 'link_children' key_dotfile_noempty = 'ignoreempty' + key_dotfile_notemplate = 'notemplate' # profile key_profile_dotfiles = 'dotfiles' @@ -83,6 +84,7 @@ class CfgYaml: key_settings_noempty = Settings.key_ignoreempty key_settings_minversion = Settings.key_minversion key_imp_link = Settings.key_link_on_import + key_settings_notemplate = Settings.key_template_dotfile_default # link values lnk_nolink = LinkTypes.NOLINK.name.lower() @@ -597,6 +599,10 @@ class CfgYaml: if self.key_dotfile_noempty not in v: val = self.settings.get(self.key_settings_noempty, False) v[self.key_dotfile_noempty] = val + # apply notemplate if undefined + if self.key_dotfile_notemplate not in v: + val = self.settings.get(self.key_settings_notemplate, False) + v[self.key_dotfile_notemplate] = val return new def _add_variables(self, new, shell=False, template=True, prio=False): diff --git a/dotdrop/dotfile.py b/dotdrop/dotfile.py index ae2025c..dd4a95b 100644 --- a/dotdrop/dotfile.py +++ b/dotdrop/dotfile.py @@ -16,12 +16,13 @@ class Dotfile(DictParser): key_noempty = 'ignoreempty' key_trans_r = 'trans_read' key_trans_w = 'trans_write' + key_notemplate = 'notemplate' def __init__(self, key, dst, src, actions=[], trans_r=None, trans_w=None, link=LinkTypes.NOLINK, noempty=False, cmpignore=[], upignore=[], - instignore=[]): + instignore=[], notemplate=False): """ constructor @key: dotfile key @@ -35,6 +36,7 @@ class Dotfile(DictParser): @upignore: patterns to ignore when updating @cmpignore: patterns to ignore when comparing @instignore: patterns to ignore when installing + @notemplate: disable template for this dotfile """ self.actions = actions self.dst = dst @@ -47,6 +49,7 @@ class Dotfile(DictParser): self.upignore = upignore self.cmpignore = cmpignore self.instignore = instignore + self.notemplate = notemplate if self.link != LinkTypes.NOLINK and \ ( @@ -91,10 +94,12 @@ class Dotfile(DictParser): value['noempty'] = value.get(cls.key_noempty, False) value['trans_r'] = value.get(cls.key_trans_r) value['trans_w'] = value.get(cls.key_trans_w) + value['notemplate'] = value.get(cls.key_notemplate, False) # remove old entries value.pop(cls.key_noempty, None) value.pop(cls.key_trans_r, None) value.pop(cls.key_trans_w, None) + value.pop(cls.key_notemplate, None) return value def __eq__(self, other): @@ -114,6 +119,7 @@ class Dotfile(DictParser): out += '\n{}src: \"{}\"'.format(indent, self.src) out += '\n{}dst: \"{}\"'.format(indent, self.dst) out += '\n{}link: \"{}\"'.format(indent, str(self.link)) + out += '\n{}notemplate: \"{}\"'.format(indent, str(self.notemplate)) out += '\n{}pre-action:'.format(indent) some = self.get_pre_actions() diff --git a/dotdrop/settings.py b/dotdrop/settings.py index 264ccc2..c38f5bc 100644 --- a/dotdrop/settings.py +++ b/dotdrop/settings.py @@ -34,6 +34,7 @@ class Settings(DictParser): key_func_file = 'func_file' key_filter_file = 'filter_file' key_diff_command = 'diff_command' + key_template_dotfile_default = 'template_dotfile_default' # import keys key_import_actions = 'import_actions' @@ -49,7 +50,8 @@ class Settings(DictParser): upignore=[], cmpignore=[], instignore=[], workdir='~/.config/dotdrop', showdiff=False, minversion=None, func_file=[], filter_file=[], - diff_command='diff -r -u {0} {1}'): + diff_command='diff -r -u {0} {1}', + template_dotfile_default=False): self.backup = backup self.banner = banner self.create = create @@ -72,6 +74,7 @@ class Settings(DictParser): self.func_file = func_file self.filter_file = filter_file self.diff_command = diff_command + self.template_dotfile_default = template_dotfile_default def _serialize_seq(self, name, dic): """serialize attribute 'name' into 'dic'""" @@ -94,6 +97,7 @@ class Settings(DictParser): self.key_workdir: self.workdir, self.key_minversion: self.minversion, self.key_diff_command: self.diff_command, + self.key_template_dotfile_default: self.template_dotfile_default, } self._serialize_seq(self.key_default_actions, dic) self._serialize_seq(self.key_import_actions, dic) From 96fcff89c8da3026f96832a616b1edc471d1e73c Mon Sep 17 00:00:00 2001 From: deadc0de6 Date: Thu, 8 Oct 2020 19:48:12 +0200 Subject: [PATCH 03/13] refactoring --- dotdrop/installer.py | 44 ++++++++++++++++++++++---------------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/dotdrop/installer.py b/dotdrop/installer.py index ef0e0a9..cd39e36 100644 --- a/dotdrop/installer.py +++ b/dotdrop/installer.py @@ -103,13 +103,13 @@ class Installer: self.log.dbg('install {} to {}'.format(src, dst)) self.log.dbg('is a directory \"{}\": {}'.format(src, isdir)) if isdir: - b, e = self._handle_dir(templater, src, dst, - actionexec=actionexec, - noempty=noempty, ignore=ignore) + b, e = self._install_dir(templater, src, dst, + actionexec=actionexec, + noempty=noempty, ignore=ignore) return self._log_install(b, e) - b, e = self._handle_file(templater, src, dst, - actionexec=actionexec, - noempty=noempty, ignore=ignore) + b, e = self._install_file(templater, src, dst, + actionexec=actionexec, + noempty=noempty, ignore=ignore) return self._log_install(b, e) def link(self, templater, src, dst, actionexec=None): @@ -306,9 +306,9 @@ class Installer: tmp['_dotfile_sub_abs_dst'] = dst return tmp - def _handle_file(self, templater, src, dst, - actionexec=None, noempty=False, - ignore=[]): + def _install_file(self, templater, src, dst, + actionexec=None, noempty=False, + ignore=[]): """install src to dst when is a file""" if self.debug: self.log.dbg('generate template for {}'.format(src)) @@ -357,9 +357,9 @@ class Installer: err = 'installing {} to {}'.format(src, dst) return False, err - def _handle_dir(self, templater, src, dst, - actionexec=None, noempty=False, - ignore=[]): + def _install_dir(self, templater, src, dst, + actionexec=None, noempty=False, + ignore=[]): """install src to dst when is a directory""" if self.debug: self.log.dbg('install dir {}'.format(src)) @@ -374,11 +374,11 @@ class Installer: f = os.path.join(src, entry) if not os.path.isdir(f): # is file - res, err = self._handle_file(templater, f, - os.path.join(dst, entry), - actionexec=actionexec, - noempty=noempty, - ignore=ignore) + res, err = self._install_file(templater, f, + os.path.join(dst, entry), + actionexec=actionexec, + noempty=noempty, + ignore=ignore) if not res and err: # error occured ret = res, err @@ -388,11 +388,11 @@ class Installer: ret = True, None else: # is directory - res, err = self._handle_dir(templater, f, - os.path.join(dst, entry), - actionexec=actionexec, - noempty=noempty, - ignore=ignore) + res, err = self._install_dir(templater, f, + os.path.join(dst, entry), + actionexec=actionexec, + noempty=noempty, + ignore=ignore) if not res and err: # error occured ret = res, err From 4b0fb646793eb026d107f0628b23f9a441c7fe91 Mon Sep 17 00:00:00 2001 From: deadc0de6 Date: Fri, 9 Oct 2020 20:12:01 +0200 Subject: [PATCH 04/13] improve tests for notemplate --- tests-ng/notemplate.sh | 152 ++++++++++++++++++++++++++++++++++++----- 1 file changed, 135 insertions(+), 17 deletions(-) diff --git a/tests-ng/notemplate.sh b/tests-ng/notemplate.sh index 6e02b1a..3da76fd 100755 --- a/tests-ng/notemplate.sh +++ b/tests-ng/notemplate.sh @@ -65,29 +65,96 @@ config: dotpath: dotfiles template_dotfile_default: true dotfiles: + f_f1: + dst: ${tmpd}/f1 + src: f1 d_d1: - dst: ${tmpd}/d1 - src: d1 + dst: ${tmpd}/dir1 + src: dir1 + d_d2: + dst: ${tmpd}/dir2 + src: dir2 + link: link + d_d3: + dst: ${tmpd}/dir3 + src: dir3 + link: link_children + f_fl: + dst: ${tmpd}/fl + src: fl + link: link profiles: p1: dotfiles: + - f_f1 - d_d1 + - d_d2 + - d_d3 + - f_fl _EOF #cat ${cfg} # create the dotfile -echo "before" > ${tmps}/dotfiles/d1 -echo "{#@@ should not be stripped @@#}" >> ${tmps}/dotfiles/d1 -echo "{{@@ header() @@}}" >> ${tmps}/dotfiles/d1 -echo "after" >> ${tmps}/dotfiles/d1 +echo "before" > ${tmps}/dotfiles/f1 +echo "{#@@ should not be stripped @@#}" >> ${tmps}/dotfiles/f1 +echo "{{@@ header() @@}}" >> ${tmps}/dotfiles/f1 +echo "after" >> ${tmps}/dotfiles/f1 + +# create the directory +mkdir -p ${tmps}/dotfiles/dir1/d1 +echo "{{@@ header() @@}}" >> ${tmps}/dotfiles/dir1/d1/f2 + +# create the linked directory +mkdir -p ${tmps}/dotfiles/dir2/d1 +echo "{{@@ header() @@}}" >> ${tmps}/dotfiles/dir2/d1/f2 + +# create the link_children directory +mkdir -p ${tmps}/dotfiles/dir3/{s1,s2,s3} +echo "{{@@ header() @@}}" >> ${tmps}/dotfiles/dir3/s1/f1 +echo "{{@@ header() @@}}" >> ${tmps}/dotfiles/dir3/s2/f2 + +# create the linked dotfile +echo "{{@@ header() @@}}" >> ${tmps}/dotfiles/fl # install +echo "doing globally" cd ${ddpath} | ${bin} install -f -c ${cfg} -p p1 -V -# test existence -[ ! -e ${tmpd}/d1 ] && echo 'not installed' && exit 1 -grep 'header' ${tmpd}/d1 || echo "header stripped" && exit 1 -grep 'should not be stripped' ${tmpd}/d1 || echo "comment stripped" && exit 1 +# simple file +echo "* test simple file" +[ ! -e ${tmpd}/f1 ] && echo 'not installed1' && exit 1 +grep 'header' ${tmpd}/f1 || (echo "header stripped" && exit 1) +grep 'should not be stripped' ${tmpd}/f1 || (echo "comment stripped" && exit 1) + +# directory +echo "* test directory" +[ ! -d ${tmpd}/dir1 ] && echo 'not installed1' && exit 1 +[ ! -d ${tmpd}/dir1/d1 ] && echo 'not installed2' && exit 1 +[ ! -e ${tmpd}/dir1/d1/f2 ] && echo 'not installed3' && exit 1 +grep 'header' ${tmpd}/dir1/d1/f2 || (echo "header stripped" && exit 1) + +# linked directory +echo "* test linked directory" +[ ! -h ${tmpd}/dir2 ] && echo 'not installed1' && exit 1 +[ ! -d ${tmpd}/dir2/d1 ] && echo 'not installed2' && exit 1 +[ ! -e ${tmpd}/dir2/d1/f2 ] && echo 'not installed3' && exit 1 +grep 'header' ${tmpd}/dir2/d1/f2 || (echo "header stripped" && exit 1) + +# children_link directory +echo "* test link_children directory" +[ ! -d ${tmpd}/dir3 ] && echo 'not installed1' && exit 1 +[ ! -h ${tmpd}/dir3/s1 ] && echo 'not installed2' && exit 1 +[ ! -h ${tmpd}/dir3/s2 ] && echo 'not installed3' && exit 1 +[ ! -h ${tmpd}/dir3/s3 ] && echo 'not installed4' && exit 1 +[ ! -e ${tmpd}/dir3/s1/f1 ] && echo 'not installed5' && exit 1 +[ ! -e ${tmpd}/dir3/s2/f2 ] && echo 'not installed6' && exit 1 +grep 'header' ${tmpd}/dir3/s1/f1 || (echo "header stripped" && exit 1) +grep 'header' ${tmpd}/dir3/s2/f2 || (echo "header stripped" && exit 1) + +# linked file +echo "* test linked file" +[ ! -h ${tmpd}/fl ] && echo 'not installed' && exit 1 +grep 'header' ${tmpd}/f1 || (echo "header stripped" && exit 1) # through the dotfile cat > ${cfg} << _EOF @@ -95,15 +162,34 @@ config: backup: true create: true dotpath: dotfiles + template_dotfile_default: true dotfiles: + f_f1: + dst: ${tmpd}/f1 + src: f1 d_d1: - dst: ${tmpd}/d1 - src: d1 - notemplate: true + dst: ${tmpd}/dir1 + src: dir1 + d_d2: + dst: ${tmpd}/dir2 + src: dir2 + link: link + d_d3: + dst: ${tmpd}/dir3 + src: dir3 + link: link_children + f_fl: + dst: ${tmpd}/fl + src: fl + link: link profiles: p1: dotfiles: + - f_f1 - d_d1 + - d_d2 + - d_d3 + - f_fl _EOF #cat ${cfg} @@ -111,12 +197,44 @@ _EOF rm -rf ${tmpd}/* # install +echo "doing specifically" cd ${ddpath} | ${bin} install -f -c ${cfg} -p p1 -V -# test existence -[ ! -e ${tmpd}/d1 ] && echo 'not installed' && exit 1 -grep 'header' ${tmpd}/d1 || echo "header stripped" && exit 1 -grep 'should not be stripped' ${tmpd}/d1 || echo "comment stripped" && exit 1 +# simple file +echo "* test simple file" +[ ! -e ${tmpd}/f1 ] && echo 'not installed1' && exit 1 +grep 'header' ${tmpd}/f1 || (echo "header stripped" && exit 1) +grep 'should not be stripped' ${tmpd}/f1 || (echo "comment stripped" && exit 1) + +# directory +echo "* test directory" +[ ! -d ${tmpd}/dir1 ] && echo 'not installed1' && exit 1 +[ ! -d ${tmpd}/dir1/d1 ] && echo 'not installed2' && exit 1 +[ ! -e ${tmpd}/dir1/d1/f2 ] && echo 'not installed3' && exit 1 +grep 'header' ${tmpd}/dir1/d1/f2 || (echo "header stripped" && exit 1) + +# linked directory +echo "* test linked directory" +[ ! -h ${tmpd}/dir2 ] && echo 'not installed1' && exit 1 +[ ! -d ${tmpd}/dir2/d1 ] && echo 'not installed2' && exit 1 +[ ! -e ${tmpd}/dir2/d1/f2 ] && echo 'not installed3' && exit 1 +grep 'header' ${tmpd}/dir2/d1/f2 || (echo "header stripped" && exit 1) + +# children_link directory +echo "* test link_children directory" +[ ! -d ${tmpd}/dir3 ] && echo 'not installed1' && exit 1 +[ ! -h ${tmpd}/dir3/s1 ] && echo 'not installed2' && exit 1 +[ ! -h ${tmpd}/dir3/s2 ] && echo 'not installed3' && exit 1 +[ ! -h ${tmpd}/dir3/s3 ] && echo 'not installed4' && exit 1 +[ ! -e ${tmpd}/dir3/s1/f1 ] && echo 'not installed5' && exit 1 +[ ! -e ${tmpd}/dir3/s2/f2 ] && echo 'not installed6' && exit 1 +grep 'header' ${tmpd}/dir3/s1/f1 || (echo "header stripped" && exit 1) +grep 'header' ${tmpd}/dir3/s2/f2 || (echo "header stripped" && exit 1) + +# linked file +echo "* test linked file" +[ ! -h ${tmpd}/fl ] && echo 'not installed' && exit 1 +grep 'header' ${tmpd}/f1 || (echo "header stripped" && exit 1) ## CLEANING rm -rf ${tmps} ${tmpd} From 4a74e71bb7e8bba68fae2b22c25456f74d353c8b Mon Sep 17 00:00:00 2001 From: deadc0de6 Date: Fri, 9 Oct 2020 20:13:16 +0200 Subject: [PATCH 05/13] implement notemplate for install --- dotdrop/cfg_yaml.py | 1 + dotdrop/dotdrop.py | 12 +++-- dotdrop/dotfile.py | 9 ++-- dotdrop/installer.py | 124 +++++++++++++++++++++++++++---------------- 4 files changed, 94 insertions(+), 52 deletions(-) diff --git a/dotdrop/cfg_yaml.py b/dotdrop/cfg_yaml.py index bd76ace..9b9947c 100644 --- a/dotdrop/cfg_yaml.py +++ b/dotdrop/cfg_yaml.py @@ -603,6 +603,7 @@ class CfgYaml: if self.key_dotfile_notemplate not in v: val = self.settings.get(self.key_settings_notemplate, False) v[self.key_dotfile_notemplate] = val + return new def _add_variables(self, new, shell=False, template=True, prio=False): diff --git a/dotdrop/dotdrop.py b/dotdrop/dotdrop.py index 26b4707..5afa246 100644 --- a/dotdrop/dotdrop.py +++ b/dotdrop/dotdrop.py @@ -129,11 +129,13 @@ def cmd_install(o): LOG.dbg(dotfile.prt()) if hasattr(dotfile, 'link') and dotfile.link == LinkTypes.LINK: r, err = inst.link(t, dotfile.src, dotfile.dst, - actionexec=pre_actions_exec) + actionexec=pre_actions_exec, + notemplate=dotfile.notemplate) elif hasattr(dotfile, 'link') and \ dotfile.link == LinkTypes.LINK_CHILDREN: r, err = inst.link_children(t, dotfile.src, dotfile.dst, - actionexec=pre_actions_exec) + actionexec=pre_actions_exec, + notemplate=dotfile.notemplate) else: src = dotfile.src tmp = None @@ -147,7 +149,8 @@ def cmd_install(o): r, err = inst.install(t, src, dotfile.dst, actionexec=pre_actions_exec, noempty=dotfile.noempty, - ignore=ignores) + ignore=ignores, + notemplate=dotfile.notemplate) if tmp: tmp = os.path.join(o.dotpath, tmp) if os.path.exists(tmp): @@ -264,7 +267,8 @@ def cmd_compare(o, tmp): continue # install dotfile to temporary dir and compare - ret, err, insttmp = inst.install_to_temp(t, tmp, src, dotfile.dst) + ret, err, insttmp = inst.install_to_temp(t, tmp, src, dotfile.dst, + notemplate=dotfile.notemplate) if not ret: # failed to install to tmp line = '=> compare {}: error' diff --git a/dotdrop/dotfile.py b/dotdrop/dotfile.py index dd4a95b..17b91a1 100644 --- a/dotdrop/dotfile.py +++ b/dotdrop/dotfile.py @@ -99,7 +99,6 @@ class Dotfile(DictParser): value.pop(cls.key_noempty, None) value.pop(cls.key_trans_r, None) value.pop(cls.key_trans_w, None) - value.pop(cls.key_notemplate, None) return value def __eq__(self, other): @@ -109,8 +108,12 @@ class Dotfile(DictParser): return hash(self.dst) ^ hash(self.src) ^ hash(self.key) def __str__(self): - msg = 'key:\"{}\", src:\"{}\", dst:\"{}\", link:\"{}\"' - return msg.format(self.key, self.src, self.dst, str(self.link)) + msg = 'key:\"{}\"'.format(self.key) + msg += ', src:\"{}\"'.format(self.src) + msg += ', dst:\"{}\"'.format(self.dst) + msg += ', link:\"{}\"'.format(str(self.link)) + msg += ', template:{}'.format(not self.notemplate) + return msg def prt(self): """extended dotfile to str""" diff --git a/dotdrop/installer.py b/dotdrop/installer.py index cd39e36..6eb8bf0 100644 --- a/dotdrop/installer.py +++ b/dotdrop/installer.py @@ -7,6 +7,7 @@ handle the installation of dotfiles import os import errno +import shutil # local imports from dotdrop.logger import Logger @@ -65,7 +66,7 @@ class Installer: def install(self, templater, src, dst, actionexec=None, noempty=False, - ignore=[]): + ignore=[], notemplate=False): """ install src to dst using a template @templater: the templater object @@ -74,6 +75,7 @@ class Installer: @actionexec: action executor callback @noempty: render empty template flag @ignore: pattern to ignore when installing + @notemplate: do not template return - True, None : success @@ -105,20 +107,23 @@ class Installer: if isdir: b, e = self._install_dir(templater, src, dst, actionexec=actionexec, - noempty=noempty, ignore=ignore) + noempty=noempty, ignore=ignore, + notemplate=notemplate) return self._log_install(b, e) b, e = self._install_file(templater, src, dst, actionexec=actionexec, - noempty=noempty, ignore=ignore) + noempty=noempty, ignore=ignore, + notemplate=notemplate) return self._log_install(b, e) - def link(self, templater, src, dst, actionexec=None): + def link(self, templater, src, dst, actionexec=None, notemplate=False): """ set src as the link target of dst @templater: the templater @src: dotfile source path in dotpath @dst: dotfile destination path in the FS @actionexec: action executor callback + @notemplate: do no template return - True, None : success @@ -140,28 +145,32 @@ class Installer: dst = os.path.normpath(os.path.expanduser(dst)) if self.totemp: # ignore actions - b, e = self.install(templater, src, dst, actionexec=None) + b, e = self.install(templater, src, dst, actionexec=None, + notemplate=notemplate) return self._log_install(b, e) - if Templategen.is_template(src): + if not notemplate and Templategen.is_template(src): 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) - i, err = self.install(templater, src, tmp, actionexec=actionexec) + i, err = self.install(templater, src, tmp, actionexec=actionexec, + notemplate=notemplate) if not i and not os.path.exists(tmp): return self._log_install(i, err) src = tmp b, e = self._link(src, dst, actionexec=actionexec) return self._log_install(b, e) - def link_children(self, templater, src, dst, actionexec=None): + def link_children(self, templater, src, dst, actionexec=None, + notemplate=False): """ link all dotfiles in a given directory @templater: the templater @src: dotfile source path in dotpath @dst: dotfile destination path in the FS @actionexec: action executor callback + @notemplate: do not template return - True, None: success @@ -221,13 +230,14 @@ class Installer: if self.debug: self.log.dbg('symlink child {} to {}'.format(src, dst)) - if Templategen.is_template(src): + if not notemplate and Templategen.is_template(src): 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) - r, e = self.install(templater, src, tmp, actionexec=actionexec) + r, e = self.install(templater, src, tmp, actionexec=actionexec, + notemplate=notemplate) if not r and e and not os.path.exists(tmp): continue src = tmp @@ -308,12 +318,14 @@ class Installer: def _install_file(self, templater, src, dst, actionexec=None, noempty=False, - ignore=[]): + ignore=[], notemplate=False): """install src to dst when is a file""" if self.debug: - self.log.dbg('generate template for {}'.format(src)) + self.log.dbg('deploy file: {}'.format(src)) self.log.dbg('ignore empty: {}'.format(noempty)) self.log.dbg('ignore pattern: {}'.format(ignore)) + self.log.dbg('no template: {}'.format(notemplate)) + self.log.dbg('no empty: {}'.format(noempty)) if utils.must_ignore([src, dst], ignore, debug=self.debug): if self.debug: @@ -324,42 +336,60 @@ class Installer: # symlink loop err = 'dotfile points to itself: {}'.format(dst) return False, err - saved = templater.add_tmp_vars(self._get_tmp_file_vars(src, dst)) - try: - content = templater.generate(src) - except UndefinedException as e: - return False, str(e) - finally: - templater.restore_vars(saved) - if noempty and utils.content_empty(content): - if self.debug: - self.log.dbg('ignoring empty template: {}'.format(src)) - return False, None - if content is None: - err = 'empty template {}'.format(src) - return False, err - if not os.path.exists(src): - err = 'source dotfile does not exist: {}'.format(src) - return False, err - st = os.stat(src) - ret, err = self._write(src, dst, content, - st.st_mode, actionexec=actionexec) - if ret < 0: - return False, err - if ret > 0: - if self.debug: - self.log.dbg('ignoring {}'.format(dst)) - return False, None + + # handle the file + if not notemplate: + # template the file + saved = templater.add_tmp_vars(self._get_tmp_file_vars(src, dst)) + try: + content = templater.generate(src) + except UndefinedException as e: + return False, str(e) + finally: + templater.restore_vars(saved) + if noempty and utils.content_empty(content): + if self.debug: + self.log.dbg('ignoring empty template: {}'.format(src)) + return False, None + if content is None: + err = 'empty template {}'.format(src) + return False, err + if not os.path.exists(src): + err = 'source dotfile does not exist: {}'.format(src) + return False, err + st = os.stat(src) + ret, err = self._write(src, dst, content, + st.st_mode, actionexec=actionexec) + # build return values + if ret < 0: + # error + return False, err + if ret > 0: + # already exists + if self.debug: + self.log.dbg('ignoring {}'.format(dst)) + return False, None + else: + # copy the file + try: + shutil.copyfile(src, dst) + shutil.copymode(src, dst) + except Exception as e: + return False, str(e) + ret = 0 + if ret == 0: + # success if not self.dry and not self.comparing: self.log.sub('copied {} to {}'.format(src, dst)) return True, None + # error err = 'installing {} to {}'.format(src, dst) return False, err def _install_dir(self, templater, src, dst, actionexec=None, noempty=False, - ignore=[]): + ignore=[], notemplate=False): """install src to dst when is a directory""" if self.debug: self.log.dbg('install dir {}'.format(src)) @@ -378,7 +408,8 @@ class Installer: os.path.join(dst, entry), actionexec=actionexec, noempty=noempty, - ignore=ignore) + ignore=ignore, + notemplate=notemplate) if not res and err: # error occured ret = res, err @@ -392,7 +423,8 @@ class Installer: os.path.join(dst, entry), actionexec=actionexec, noempty=noempty, - ignore=ignore) + ignore=ignore, + notemplate=notemplate) if not res and err: # error occured ret = res, err @@ -530,12 +562,13 @@ class Installer: self.action_executed = True return ret, err - def _install_to_temp(self, templater, src, dst, tmpdir): + def _install_to_temp(self, templater, src, dst, tmpdir, notemplate=False): """install a dotfile to a tempdir""" tmpdst = self._pivot_path(dst, tmpdir) - return self.install(templater, src, tmpdst), tmpdst + r = self.install(templater, src, tmpdst, notemplate=notemplate) + return r, tmpdst - def install_to_temp(self, templater, tmpdir, src, dst): + def install_to_temp(self, templater, tmpdir, src, dst, notemplate=False): """install a dotfile to a tempdir""" ret = False tmpdst = '' @@ -553,7 +586,8 @@ class Installer: if self.debug: self.log.dbg('tmp install {} (defined dst: {})'.format(src, dst)) # install the dotfile to a temp directory for comparing - r, tmpdst = self._install_to_temp(templater, src, dst, tmpdir) + r, tmpdst = self._install_to_temp(templater, src, dst, tmpdir, + notemplate=notemplate) ret, err = r if self.debug: self.log.dbg('tmp installed in {}'.format(tmpdst)) From 971c76d95de2ee14787e646c269285a6e6d8cb0e Mon Sep 17 00:00:00 2001 From: deadc0de6 Date: Fri, 9 Oct 2020 21:02:07 +0200 Subject: [PATCH 06/13] fix tests --- dotdrop/cfg_yaml.py | 12 +++++------ dotdrop/dotdrop.py | 8 ++++---- dotdrop/dotfile.py | 14 ++++++------- dotdrop/installer.py | 46 +++++++++++++++++++++--------------------- dotdrop/settings.py | 2 +- tests-ng/notemplate.sh | 35 +++++++++++++++++++++++++++++--- 6 files changed, 73 insertions(+), 44 deletions(-) diff --git a/dotdrop/cfg_yaml.py b/dotdrop/cfg_yaml.py index 9b9947c..da47e55 100644 --- a/dotdrop/cfg_yaml.py +++ b/dotdrop/cfg_yaml.py @@ -58,7 +58,7 @@ class CfgYaml: key_dotfile_actions = 'actions' key_dotfile_link_children = 'link_children' key_dotfile_noempty = 'ignoreempty' - key_dotfile_notemplate = 'notemplate' + key_dotfile_template = 'template' # profile key_profile_dotfiles = 'dotfiles' @@ -84,7 +84,7 @@ class CfgYaml: key_settings_noempty = Settings.key_ignoreempty key_settings_minversion = Settings.key_minversion key_imp_link = Settings.key_link_on_import - key_settings_notemplate = Settings.key_template_dotfile_default + key_settings_template = Settings.key_template_dotfile_default # link values lnk_nolink = LinkTypes.NOLINK.name.lower() @@ -599,10 +599,10 @@ class CfgYaml: if self.key_dotfile_noempty not in v: val = self.settings.get(self.key_settings_noempty, False) v[self.key_dotfile_noempty] = val - # apply notemplate if undefined - if self.key_dotfile_notemplate not in v: - val = self.settings.get(self.key_settings_notemplate, False) - v[self.key_dotfile_notemplate] = val + # apply template if undefined + if self.key_dotfile_template not in v: + val = self.settings.get(self.key_settings_template, True) + v[self.key_dotfile_template] = val return new diff --git a/dotdrop/dotdrop.py b/dotdrop/dotdrop.py index 5afa246..729dead 100644 --- a/dotdrop/dotdrop.py +++ b/dotdrop/dotdrop.py @@ -130,12 +130,12 @@ def cmd_install(o): if hasattr(dotfile, 'link') and dotfile.link == LinkTypes.LINK: r, err = inst.link(t, dotfile.src, dotfile.dst, actionexec=pre_actions_exec, - notemplate=dotfile.notemplate) + template=dotfile.template) elif hasattr(dotfile, 'link') and \ dotfile.link == LinkTypes.LINK_CHILDREN: r, err = inst.link_children(t, dotfile.src, dotfile.dst, actionexec=pre_actions_exec, - notemplate=dotfile.notemplate) + template=dotfile.template) else: src = dotfile.src tmp = None @@ -150,7 +150,7 @@ def cmd_install(o): actionexec=pre_actions_exec, noempty=dotfile.noempty, ignore=ignores, - notemplate=dotfile.notemplate) + template=dotfile.template) if tmp: tmp = os.path.join(o.dotpath, tmp) if os.path.exists(tmp): @@ -268,7 +268,7 @@ def cmd_compare(o, tmp): # install dotfile to temporary dir and compare ret, err, insttmp = inst.install_to_temp(t, tmp, src, dotfile.dst, - notemplate=dotfile.notemplate) + template=dotfile.template) if not ret: # failed to install to tmp line = '=> compare {}: error' diff --git a/dotdrop/dotfile.py b/dotdrop/dotfile.py index 17b91a1..da7890d 100644 --- a/dotdrop/dotfile.py +++ b/dotdrop/dotfile.py @@ -16,13 +16,13 @@ class Dotfile(DictParser): key_noempty = 'ignoreempty' key_trans_r = 'trans_read' key_trans_w = 'trans_write' - key_notemplate = 'notemplate' + key_template = 'template' def __init__(self, key, dst, src, actions=[], trans_r=None, trans_w=None, link=LinkTypes.NOLINK, noempty=False, cmpignore=[], upignore=[], - instignore=[], notemplate=False): + instignore=[], template=True): """ constructor @key: dotfile key @@ -36,7 +36,7 @@ class Dotfile(DictParser): @upignore: patterns to ignore when updating @cmpignore: patterns to ignore when comparing @instignore: patterns to ignore when installing - @notemplate: disable template for this dotfile + @template: template this dotfile """ self.actions = actions self.dst = dst @@ -49,7 +49,7 @@ class Dotfile(DictParser): self.upignore = upignore self.cmpignore = cmpignore self.instignore = instignore - self.notemplate = notemplate + self.template = template if self.link != LinkTypes.NOLINK and \ ( @@ -94,7 +94,7 @@ class Dotfile(DictParser): value['noempty'] = value.get(cls.key_noempty, False) value['trans_r'] = value.get(cls.key_trans_r) value['trans_w'] = value.get(cls.key_trans_w) - value['notemplate'] = value.get(cls.key_notemplate, False) + value['template'] = value.get(cls.key_template, True) # remove old entries value.pop(cls.key_noempty, None) value.pop(cls.key_trans_r, None) @@ -112,7 +112,7 @@ class Dotfile(DictParser): msg += ', src:\"{}\"'.format(self.src) msg += ', dst:\"{}\"'.format(self.dst) msg += ', link:\"{}\"'.format(str(self.link)) - msg += ', template:{}'.format(not self.notemplate) + msg += ', template:{}'.format(self.template) return msg def prt(self): @@ -122,7 +122,7 @@ class Dotfile(DictParser): out += '\n{}src: \"{}\"'.format(indent, self.src) out += '\n{}dst: \"{}\"'.format(indent, self.dst) out += '\n{}link: \"{}\"'.format(indent, str(self.link)) - out += '\n{}notemplate: \"{}\"'.format(indent, str(self.notemplate)) + out += '\n{}template: \"{}\"'.format(indent, str(self.template)) out += '\n{}pre-action:'.format(indent) some = self.get_pre_actions() diff --git a/dotdrop/installer.py b/dotdrop/installer.py index 6eb8bf0..1e8e2db 100644 --- a/dotdrop/installer.py +++ b/dotdrop/installer.py @@ -66,7 +66,7 @@ class Installer: def install(self, templater, src, dst, actionexec=None, noempty=False, - ignore=[], notemplate=False): + ignore=[], template=True): """ install src to dst using a template @templater: the templater object @@ -75,7 +75,7 @@ class Installer: @actionexec: action executor callback @noempty: render empty template flag @ignore: pattern to ignore when installing - @notemplate: do not template + @template: template this dotfile return - True, None : success @@ -108,22 +108,22 @@ class Installer: b, e = self._install_dir(templater, src, dst, actionexec=actionexec, noempty=noempty, ignore=ignore, - notemplate=notemplate) + template=template) return self._log_install(b, e) b, e = self._install_file(templater, src, dst, actionexec=actionexec, noempty=noempty, ignore=ignore, - notemplate=notemplate) + template=template) return self._log_install(b, e) - def link(self, templater, src, dst, actionexec=None, notemplate=False): + def link(self, templater, src, dst, actionexec=None, template=True): """ set src as the link target of dst @templater: the templater @src: dotfile source path in dotpath @dst: dotfile destination path in the FS @actionexec: action executor callback - @notemplate: do no template + @template: template this dotfile return - True, None : success @@ -146,16 +146,16 @@ class Installer: if self.totemp: # ignore actions b, e = self.install(templater, src, dst, actionexec=None, - notemplate=notemplate) + template=template) return self._log_install(b, e) - if not notemplate and Templategen.is_template(src): + if template and Templategen.is_template(src): 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) i, err = self.install(templater, src, tmp, actionexec=actionexec, - notemplate=notemplate) + template=template) if not i and not os.path.exists(tmp): return self._log_install(i, err) src = tmp @@ -163,14 +163,14 @@ class Installer: return self._log_install(b, e) def link_children(self, templater, src, dst, actionexec=None, - notemplate=False): + template=True): """ link all dotfiles in a given directory @templater: the templater @src: dotfile source path in dotpath @dst: dotfile destination path in the FS @actionexec: action executor callback - @notemplate: do not template + @template: template this dotfile return - True, None: success @@ -230,14 +230,14 @@ class Installer: if self.debug: self.log.dbg('symlink child {} to {}'.format(src, dst)) - if not notemplate and Templategen.is_template(src): + if template and Templategen.is_template(src): 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) r, e = self.install(templater, src, tmp, actionexec=actionexec, - notemplate=notemplate) + template=template) if not r and e and not os.path.exists(tmp): continue src = tmp @@ -318,13 +318,13 @@ class Installer: def _install_file(self, templater, src, dst, actionexec=None, noempty=False, - ignore=[], notemplate=False): + ignore=[], template=True): """install src to dst when is a file""" if self.debug: self.log.dbg('deploy file: {}'.format(src)) self.log.dbg('ignore empty: {}'.format(noempty)) self.log.dbg('ignore pattern: {}'.format(ignore)) - self.log.dbg('no template: {}'.format(notemplate)) + self.log.dbg('template: {}'.format(template)) self.log.dbg('no empty: {}'.format(noempty)) if utils.must_ignore([src, dst], ignore, debug=self.debug): @@ -338,7 +338,7 @@ class Installer: return False, err # handle the file - if not notemplate: + if template: # template the file saved = templater.add_tmp_vars(self._get_tmp_file_vars(src, dst)) try: @@ -389,7 +389,7 @@ class Installer: def _install_dir(self, templater, src, dst, actionexec=None, noempty=False, - ignore=[], notemplate=False): + ignore=[], template=True): """install src to dst when is a directory""" if self.debug: self.log.dbg('install dir {}'.format(src)) @@ -409,7 +409,7 @@ class Installer: actionexec=actionexec, noempty=noempty, ignore=ignore, - notemplate=notemplate) + template=template) if not res and err: # error occured ret = res, err @@ -424,7 +424,7 @@ class Installer: actionexec=actionexec, noempty=noempty, ignore=ignore, - notemplate=notemplate) + template=template) if not res and err: # error occured ret = res, err @@ -562,13 +562,13 @@ class Installer: self.action_executed = True return ret, err - def _install_to_temp(self, templater, src, dst, tmpdir, notemplate=False): + def _install_to_temp(self, templater, src, dst, tmpdir, template=True): """install a dotfile to a tempdir""" tmpdst = self._pivot_path(dst, tmpdir) - r = self.install(templater, src, tmpdst, notemplate=notemplate) + r = self.install(templater, src, tmpdst, template=template) return r, tmpdst - def install_to_temp(self, templater, tmpdir, src, dst, notemplate=False): + def install_to_temp(self, templater, tmpdir, src, dst, template=True): """install a dotfile to a tempdir""" ret = False tmpdst = '' @@ -587,7 +587,7 @@ class Installer: self.log.dbg('tmp install {} (defined dst: {})'.format(src, dst)) # install the dotfile to a temp directory for comparing r, tmpdst = self._install_to_temp(templater, src, dst, tmpdir, - notemplate=notemplate) + template=template) ret, err = r if self.debug: self.log.dbg('tmp installed in {}'.format(tmpdst)) diff --git a/dotdrop/settings.py b/dotdrop/settings.py index c38f5bc..64d0853 100644 --- a/dotdrop/settings.py +++ b/dotdrop/settings.py @@ -51,7 +51,7 @@ class Settings(DictParser): workdir='~/.config/dotdrop', showdiff=False, minversion=None, func_file=[], filter_file=[], diff_command='diff -r -u {0} {1}', - template_dotfile_default=False): + template_dotfile_default=True): self.backup = backup self.banner = banner self.create = create diff --git a/tests-ng/notemplate.sh b/tests-ng/notemplate.sh index 3da76fd..2eae65d 100755 --- a/tests-ng/notemplate.sh +++ b/tests-ng/notemplate.sh @@ -63,7 +63,7 @@ config: backup: true create: true dotpath: dotfiles - template_dotfile_default: true + template_dotfile_default: false dotfiles: f_f1: dst: ${tmpd}/f1 @@ -83,6 +83,10 @@ dotfiles: dst: ${tmpd}/fl src: fl link: link + f_fn: + dst: ${tmpd}/fn + src: fn + template: true profiles: p1: dotfiles: @@ -91,6 +95,7 @@ profiles: - d_d2 - d_d3 - f_fl + - f_fn _EOF #cat ${cfg} @@ -116,11 +121,16 @@ echo "{{@@ header() @@}}" >> ${tmps}/dotfiles/dir3/s2/f2 # create the linked dotfile echo "{{@@ header() @@}}" >> ${tmps}/dotfiles/fl +# create the normal dotfile +echo "before" > ${tmps}/dotfiles/fn +echo "{#@@ should not be stripped @@#}" >> ${tmps}/dotfiles/fn +echo "after" > ${tmps}/dotfiles/fn + # install -echo "doing globally" cd ${ddpath} | ${bin} install -f -c ${cfg} -p p1 -V # simple file +echo "doing globally" echo "* test simple file" [ ! -e ${tmpd}/f1 ] && echo 'not installed1' && exit 1 grep 'header' ${tmpd}/f1 || (echo "header stripped" && exit 1) @@ -156,6 +166,11 @@ echo "* test linked file" [ ! -h ${tmpd}/fl ] && echo 'not installed' && exit 1 grep 'header' ${tmpd}/f1 || (echo "header stripped" && exit 1) +# normal dotfile +echo "* normal dotfile" +[ ! -e ${tmpd}/fn ] && echo 'not installed' && exit 1 +grep 'should not be stripped' ${tmpd}/fn && echo "no templated" && exit 1 + # through the dotfile cat > ${cfg} << _EOF config: @@ -167,21 +182,29 @@ dotfiles: f_f1: dst: ${tmpd}/f1 src: f1 + template: false d_d1: dst: ${tmpd}/dir1 src: dir1 + template: false d_d2: dst: ${tmpd}/dir2 src: dir2 link: link + template: false d_d3: dst: ${tmpd}/dir3 src: dir3 link: link_children + template: false f_fl: dst: ${tmpd}/fl src: fl link: link + template: false + f_fn: + dst: ${tmpd}/fn + src: fn profiles: p1: dotfiles: @@ -190,6 +213,7 @@ profiles: - d_d2 - d_d3 - f_fl + - f_fn _EOF #cat ${cfg} @@ -197,10 +221,10 @@ _EOF rm -rf ${tmpd}/* # install -echo "doing specifically" cd ${ddpath} | ${bin} install -f -c ${cfg} -p p1 -V # simple file +echo "doing specifically" echo "* test simple file" [ ! -e ${tmpd}/f1 ] && echo 'not installed1' && exit 1 grep 'header' ${tmpd}/f1 || (echo "header stripped" && exit 1) @@ -236,6 +260,11 @@ echo "* test linked file" [ ! -h ${tmpd}/fl ] && echo 'not installed' && exit 1 grep 'header' ${tmpd}/f1 || (echo "header stripped" && exit 1) +# normal dotfile +echo "* normal dotfile" +[ ! -e ${tmpd}/fn ] && echo 'not installed' && exit 1 +grep 'should not be stripped' ${tmpd}/fn && echo "no templated" && exit 1 + ## CLEANING rm -rf ${tmps} ${tmpd} From 412236c7ff834fec0094b54035bb88f4500751a6 Mon Sep 17 00:00:00 2001 From: deadc0de6 Date: Fri, 9 Oct 2020 21:04:29 +0200 Subject: [PATCH 07/13] adding template options in doc --- docs/config-format.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/config-format.md b/docs/config-format.md index 5d32131..f42191a 100644 --- a/docs/config-format.md +++ b/docs/config-format.md @@ -31,6 +31,7 @@ Entry | Description | Default `longkey` | use long keys for dotfiles when importing (see [Import dotfiles](usage.md#import-dotfiles)) | false `minversion` | (*for internal use, do not modify*) provides the minimal dotdrop version to use | - `showdiff` | on install show a diff before asking to overwrite (see `--showdiff`) | false +`template_dotfile_default` | disable templating on all dotfiles when set to false | true `upignore` | list of patterns to ignore when updating, apply to all dotfiles (enclose in quotes when using wildcards, see [ignore patterns](config.md#ignore-patterns)) | - `workdir` | path to the directory where templates are installed before being symlinked when using `link:link` or `link:link_children` (absolute path or relative to the config file location) | `~/.config/dotdrop` link_by_default | when importing a dotfile set `link` to that value per default | false @@ -48,6 +49,7 @@ Entry | Description `cmpignore` | list of patterns to ignore when comparing (enclose in quotes when using wildcards, see [ignore patterns](config.md#ignore-patterns)) `ignoreempty` | if true empty template will not be deployed (defaults to value of `ignoreempty`) `instignore` | list of patterns to ignore when installing (enclose in quotes when using wildcards, see [ignore patterns](config.md#ignore-patterns)) +`template` | if false disable template for this dotfile (defaults to value of `template_dotfile_default`) `trans_read` | transformation key to apply when installing this dotfile (must be defined in the **trans_read** entry below, see [transformations](config-details.md#entry-transformations)) `trans_write` | transformation key to apply when updating this dotfile (must be defined in the **trans_write** entry below, see [transformations](config-details.md#entry-transformations)) `upignore` | list of patterns to ignore when updating (enclose in quotes when using wildcards, see [ignore patterns](config.md#ignore-patterns)) From ea66b8c32d3e74f7317ca85d30d84e0606cb2bf9 Mon Sep 17 00:00:00 2001 From: deadc0de6 Date: Fri, 9 Oct 2020 21:58:24 +0200 Subject: [PATCH 08/13] update doc --- docs/howto/symlink-dotfiles.md | 4 ++-- docs/templating.md | 13 +++++++++++++ 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/docs/howto/symlink-dotfiles.md b/docs/howto/symlink-dotfiles.md index 47bf442..bf12068 100644 --- a/docs/howto/symlink-dotfiles.md +++ b/docs/howto/symlink-dotfiles.md @@ -13,8 +13,8 @@ Note that if the dotfile is using template directives, it will be symlinked into `~/.config/dotdrop` instead of directly into your *dotpath* (see [Templating symlinked dotfiles](#templating-symlinked-dotfiles)) -Although the config entries `link_on_import` and `link_dotfile_default` can be set to `link_children`, it -is not recommended since operations on a dotfile that is not a directory with the option `link_children` +Although the config entries `link_on_import` and `link_dotfile_default` can be set to the value `link_children`, +it is not recommended since operations on a dotfile that is not a directory with the option `link_children` will fail. ## Symlink a dotfile diff --git a/docs/templating.md b/docs/templating.md index 975f9c2..18de338 100644 --- a/docs/templating.md +++ b/docs/templating.md @@ -4,6 +4,19 @@ Dotdrop leverage the power of [jinja2](https://palletsprojects.com/p/jinja/) to templating of dotfiles. See [jinja2 template doc](https://jinja.palletsprojects.com/en/2.11.x/templates/) or the below sections for more information on how to template your dotfiles. +## Templating or not templating + +The dotfile config entry [template](config-format.md#dotfiles-entry) +and the global config entry [template_dotfile_default](config-format.md#config-entry) +allow to control if a dotfile is being process by the templating engine. + +Obviously if the dotfile uses template directives, it needs to be templated. However if it +is not, disabling templating will speed up its installation (since it won't have to be +processed by the engine). + +For dotfiles being symlinked (`link` or `link_children`), see +[the dedicated doc](howto/symlink-dotfiles.md#templating-symlinked-dotfiles) + ## Delimiters Dotdrop uses different delimiters than From cfc0f92f1093c5aa79812ff6870535c348bc11cc Mon Sep 17 00:00:00 2001 From: deadc0de6 Date: Fri, 9 Oct 2020 21:59:53 +0200 Subject: [PATCH 09/13] update doc --- docs/config-format.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/config-format.md b/docs/config-format.md index f42191a..666e5a3 100644 --- a/docs/config-format.md +++ b/docs/config-format.md @@ -71,6 +71,7 @@ Entry | Description - "" actions: - + template: (true|false) trans_read: trans_write: ``` From 11f6a98d9c991a9fac30ae1de4ecf7fc478ed2a6 Mon Sep 17 00:00:00 2001 From: deadc0de6 Date: Fri, 9 Oct 2020 22:48:04 +0200 Subject: [PATCH 10/13] fix tests notemplate --- tests-ng/notemplate.sh | 30 ++++++++++++++++++++++-------- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/tests-ng/notemplate.sh b/tests-ng/notemplate.sh index 2eae65d..4e5a72c 100755 --- a/tests-ng/notemplate.sh +++ b/tests-ng/notemplate.sh @@ -107,27 +107,28 @@ echo "after" >> ${tmps}/dotfiles/f1 # create the directory mkdir -p ${tmps}/dotfiles/dir1/d1 -echo "{{@@ header() @@}}" >> ${tmps}/dotfiles/dir1/d1/f2 +echo "{{@@ header() @@}}" > ${tmps}/dotfiles/dir1/d1/f2 # create the linked directory mkdir -p ${tmps}/dotfiles/dir2/d1 -echo "{{@@ header() @@}}" >> ${tmps}/dotfiles/dir2/d1/f2 +echo "{{@@ header() @@}}" > ${tmps}/dotfiles/dir2/d1/f2 # create the link_children directory mkdir -p ${tmps}/dotfiles/dir3/{s1,s2,s3} -echo "{{@@ header() @@}}" >> ${tmps}/dotfiles/dir3/s1/f1 -echo "{{@@ header() @@}}" >> ${tmps}/dotfiles/dir3/s2/f2 +echo "{{@@ header() @@}}" > ${tmps}/dotfiles/dir3/s1/f1 +echo "{{@@ header() @@}}" > ${tmps}/dotfiles/dir3/s2/f2 # create the linked dotfile -echo "{{@@ header() @@}}" >> ${tmps}/dotfiles/fl +echo "{{@@ header() @@}}" > ${tmps}/dotfiles/fl # create the normal dotfile echo "before" > ${tmps}/dotfiles/fn echo "{#@@ should not be stripped @@#}" >> ${tmps}/dotfiles/fn -echo "after" > ${tmps}/dotfiles/fn +echo "after" >> ${tmps}/dotfiles/fn # install -cd ${ddpath} | ${bin} install -f -c ${cfg} -p p1 -V +cd ${ddpath} | ${bin} install -f --showdiff -c ${cfg} -p p1 -V +cd ${ddpath} | ${bin} compare -c ${cfg} -p p1 -V # simple file echo "doing globally" @@ -171,6 +172,17 @@ echo "* normal dotfile" [ ! -e ${tmpd}/fn ] && echo 'not installed' && exit 1 grep 'should not be stripped' ${tmpd}/fn && echo "no templated" && exit 1 +# test backup done +echo "before" > ${tmps}/dotfiles/f1 +cd ${ddpath} | ${bin} install -f --showdiff -c ${cfg} -p p1 -V +[ ! -e ${tmpd}/f1.dotdropbak ] && echo "backup not done" && exit 1 + +# re-create the dotfile +echo "before" > ${tmps}/dotfiles/f1 +echo "{#@@ should not be stripped @@#}" >> ${tmps}/dotfiles/f1 +echo "{{@@ header() @@}}" >> ${tmps}/dotfiles/f1 +echo "after" >> ${tmps}/dotfiles/f1 + # through the dotfile cat > ${cfg} << _EOF config: @@ -221,7 +233,8 @@ _EOF rm -rf ${tmpd}/* # install -cd ${ddpath} | ${bin} install -f -c ${cfg} -p p1 -V +cd ${ddpath} | ${bin} install -f --showdiff -c ${cfg} -p p1 -V +cd ${ddpath} | ${bin} compare -c ${cfg} -p p1 -V # simple file echo "doing specifically" @@ -265,6 +278,7 @@ echo "* normal dotfile" [ ! -e ${tmpd}/fn ] && echo 'not installed' && exit 1 grep 'should not be stripped' ${tmpd}/fn && echo "no templated" && exit 1 + ## CLEANING rm -rf ${tmps} ${tmpd} From bcfa934c5de5c504f752d0facd05e99d68c88fa1 Mon Sep 17 00:00:00 2001 From: deadc0de6 Date: Fri, 9 Oct 2020 22:48:51 +0200 Subject: [PATCH 11/13] fix notemplate installation --- dotdrop/installer.py | 120 ++++++++++++++++++++++++++----------------- dotdrop/utils.py | 2 +- 2 files changed, 74 insertions(+), 48 deletions(-) diff --git a/dotdrop/installer.py b/dotdrop/installer.py index 1e8e2db..eed2ec6 100644 --- a/dotdrop/installer.py +++ b/dotdrop/installer.py @@ -274,7 +274,7 @@ class Installer: self.log.dry('would remove {} and link to {}'.format(dst, src)) return True, None if self.showdiff: - self._diff_before_write(src, dst) + self._diff_before_write(src, dst, quiet=False) msg = 'Remove "{}" for link creation?'.format(dst) if self.safe and not self.log.ask(msg): err = 'ignoring "{}", link was not created'.format(dst) @@ -337,7 +337,12 @@ class Installer: err = 'dotfile points to itself: {}'.format(dst) return False, err + if not os.path.exists(src): + err = 'source dotfile does not exist: {}'.format(src) + return False, err + # handle the file + content = None if template: # template the file saved = templater.add_tmp_vars(self._get_tmp_file_vars(src, dst)) @@ -354,30 +359,20 @@ class Installer: if content is None: err = 'empty template {}'.format(src) return False, err - if not os.path.exists(src): - err = 'source dotfile does not exist: {}'.format(src) - return False, err - st = os.stat(src) - ret, err = self._write(src, dst, content, - st.st_mode, actionexec=actionexec) - # build return values - if ret < 0: - # error - return False, err - if ret > 0: - # already exists - if self.debug: - self.log.dbg('ignoring {}'.format(dst)) - return False, None - else: - # copy the file - try: - shutil.copyfile(src, dst) - shutil.copymode(src, dst) - except Exception as e: - return False, str(e) - ret = 0 + ret, err = self._write(src, dst, + content=content, + actionexec=actionexec, + template=template) + # build return values + if ret < 0: + # error + return False, err + if ret > 0: + # already exists + if self.debug: + self.log.dbg('ignoring {}'.format(dst)) + return False, None if ret == 0: # success if not self.dry and not self.comparing: @@ -435,22 +430,31 @@ class Installer: return ret def _fake_diff(self, dst, content): - """fake diff by comparing file content with content""" + """ + fake diff by comparing file content with content + returns True if same + """ cur = '' with open(dst, 'br') as f: cur = f.read() return cur == content - def _write(self, src, dst, content, rights, actionexec=None): - """write content to file + def _write(self, src, dst, content=None, + actionexec=None, template=True): + """ + copy dotfile / write content to file return 0, None: for success, 1, None: when already exists - -1, err: when error""" + -1, err: when error + content is always empty if template is False + and is to be ignored + """ overwrite = not self.safe if self.dry: self.log.dry('would install {}'.format(dst)) return 0, None if os.path.lexists(dst): + rights = os.stat(src).st_mode samerights = False try: samerights = os.stat(dst).st_mode == rights @@ -459,15 +463,24 @@ class Installer: # broken symlink err = 'broken symlink {}'.format(dst) return -1, err - if self.diff and self._fake_diff(dst, content) and samerights: - if self.debug: - self.log.dbg('{} is the same'.format(dst)) - return 1, None + diff = None + if self.diff: + diff = self._diff_before_write(src, dst, + content=content, + quiet=True) + if not diff and samerights: + if self.debug: + self.log.dbg('{} is the same'.format(dst)) + return 1, None if self.safe: if self.debug: self.log.dbg('change detected for {}'.format(dst)) if self.showdiff: - self._diff_before_write(src, dst, content=content) + if diff is None: + diff = self._diff_before_write(src, dst, + content=content, + quiet=True) + self._print_diff(src, dst, diff) if not self.log.ask('Overwrite \"{}\"'.format(dst)): self.log.warn('ignoring {}'.format(dst)) return 1, None @@ -482,24 +495,35 @@ class Installer: if not r: return -1, e if self.debug: - self.log.dbg('writes content to \"{}\"'.format(dst)) + self.log.dbg('install dotfile to \"{}\"'.format(dst)) # re-check in case action created the file if self.safe and not overwrite and os.path.lexists(dst): if not self.log.ask('Overwrite \"{}\"'.format(dst)): self.log.warn('ignoring {}'.format(dst)) return 1, None - # write the file - try: - with open(dst, 'wb') as f: - f.write(content) - except NotADirectoryError as e: - err = 'opening dest file: {}'.format(e) - return -1, err - os.chmod(dst, rights) + + if template: + # write content the file + try: + with open(dst, 'wb') as f: + f.write(content) + shutil.copymode(src, dst) + except NotADirectoryError as e: + err = 'opening dest file: {}'.format(e) + return -1, err + except Exception as e: + return -1, str(e) + else: + # copy file + try: + shutil.copyfile(src, dst) + shutil.copymode(src, dst) + except Exception as e: + return -1, str(e) return 0, None - def _diff_before_write(self, src, dst, content=None): - """diff before writing when using --showdiff - not efficient""" + def _diff_before_write(self, src, dst, content=None, quiet=False): + """diff before writing""" tmp = None if content: tmp = utils.write_to_tmpfile(content) @@ -509,9 +533,11 @@ class Installer: if tmp: utils.remove(tmp, quiet=True) - # fake the output for readability - if not diff: - return + if not quiet: + self._print_diff(src, dst, diff) + return diff + + def _print_diff(self, src, dst, diff): self.log.log('diff \"{}\" VS \"{}\"'.format(dst, src)) self.log.emph(diff) diff --git a/dotdrop/utils.py b/dotdrop/utils.py index 705c3fc..61f3d44 100644 --- a/dotdrop/utils.py +++ b/dotdrop/utils.py @@ -71,7 +71,7 @@ def shell(cmd, debug=False): def diff(original, modified, raw=True, diff_cmd='', debug=False): - """compare two files""" + """compare two files, returns '' if same""" if not diff_cmd: diff_cmd = 'diff -r -u {0} {1}' From 1f45334e0343d054141c22fbb2e0488e3ffc0150 Mon Sep 17 00:00:00 2001 From: deadc0de6 Date: Fri, 9 Oct 2020 23:05:08 +0200 Subject: [PATCH 12/13] fix diff printing --- dotdrop/installer.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/dotdrop/installer.py b/dotdrop/installer.py index eed2ec6..6f1cb73 100644 --- a/dotdrop/installer.py +++ b/dotdrop/installer.py @@ -165,7 +165,7 @@ class Installer: def link_children(self, templater, src, dst, actionexec=None, template=True): """ - link all dotfiles in a given directory + link all files under a given directory @templater: the templater @src: dotfile source path in dotpath @dst: dotfile destination path in the FS @@ -477,10 +477,12 @@ class Installer: self.log.dbg('change detected for {}'.format(dst)) if self.showdiff: if diff is None: + # get diff diff = self._diff_before_write(src, dst, content=content, quiet=True) - self._print_diff(src, dst, diff) + if diff: + self._print_diff(src, dst, diff) if not self.log.ask('Overwrite \"{}\"'.format(dst)): self.log.warn('ignoring {}'.format(dst)) return 1, None @@ -523,7 +525,11 @@ class Installer: return 0, None def _diff_before_write(self, src, dst, content=None, quiet=False): - """diff before writing""" + """ + diff before writing + using a temp file if content is not None + returns diff string ('' if same) + """ tmp = None if content: tmp = utils.write_to_tmpfile(content) @@ -533,11 +539,12 @@ class Installer: if tmp: utils.remove(tmp, quiet=True) - if not quiet: + if not quiet and diff: self._print_diff(src, dst, diff) return diff def _print_diff(self, src, dst, diff): + """show diff to user""" self.log.log('diff \"{}\" VS \"{}\"'.format(dst, src)) self.log.emph(diff) From 9de44901772c50ac2f8158b8921bfdf027e577d6 Mon Sep 17 00:00:00 2001 From: deadc0de6 Date: Sat, 10 Oct 2020 08:17:38 +0200 Subject: [PATCH 13/13] tests --- tests-ng/compare-ignore-relative.sh | 2 +- tests-ng/compare-ignore.sh | 2 +- tests-ng/corner-case.sh | 2 +- tests-ng/diff-cmd.sh | 2 +- tests-ng/global-compare-ignore.sh | 2 +- tests-ng/global-update-ignore.sh | 2 +- tests-ng/install-ignore.sh | 2 +- tests-ng/update-ignore-relative.sh | 2 +- tests-ng/update-ignore.sh | 2 +- 9 files changed, 9 insertions(+), 9 deletions(-) diff --git a/tests-ng/compare-ignore-relative.sh b/tests-ng/compare-ignore-relative.sh index a8c1260..4b9278c 100755 --- a/tests-ng/compare-ignore-relative.sh +++ b/tests-ng/compare-ignore-relative.sh @@ -7,7 +7,7 @@ # # exit on first error -#set -e +set -e # all this crap to get current path rl="readlink -f" diff --git a/tests-ng/compare-ignore.sh b/tests-ng/compare-ignore.sh index 53d86c9..cf162c4 100755 --- a/tests-ng/compare-ignore.sh +++ b/tests-ng/compare-ignore.sh @@ -7,7 +7,7 @@ # # exit on first error -#set -e +set -e # all this crap to get current path rl="readlink -f" diff --git a/tests-ng/corner-case.sh b/tests-ng/corner-case.sh index 1bc4e2a..bea39c1 100755 --- a/tests-ng/corner-case.sh +++ b/tests-ng/corner-case.sh @@ -12,7 +12,7 @@ # # exit on first error -#set -e +set -e # all this crap to get current path rl="readlink -f" diff --git a/tests-ng/diff-cmd.sh b/tests-ng/diff-cmd.sh index b09378d..166135d 100755 --- a/tests-ng/diff-cmd.sh +++ b/tests-ng/diff-cmd.sh @@ -7,7 +7,7 @@ # # exit on first error -#set -e +set -e # all this crap to get current path rl="readlink -f" diff --git a/tests-ng/global-compare-ignore.sh b/tests-ng/global-compare-ignore.sh index 056be9a..5f9ef10 100755 --- a/tests-ng/global-compare-ignore.sh +++ b/tests-ng/global-compare-ignore.sh @@ -7,7 +7,7 @@ # # exit on first error -#set -e +set -e # all this crap to get current path rl="readlink -f" diff --git a/tests-ng/global-update-ignore.sh b/tests-ng/global-update-ignore.sh index 5a387a6..4983d96 100755 --- a/tests-ng/global-update-ignore.sh +++ b/tests-ng/global-update-ignore.sh @@ -7,7 +7,7 @@ # # exit on first error -#set -e +set -e # all this crap to get current path rl="readlink -f" diff --git a/tests-ng/install-ignore.sh b/tests-ng/install-ignore.sh index adc4298..64bdab2 100755 --- a/tests-ng/install-ignore.sh +++ b/tests-ng/install-ignore.sh @@ -7,7 +7,7 @@ # # exit on first error -#set -e +set -e # all this crap to get current path rl="readlink -f" diff --git a/tests-ng/update-ignore-relative.sh b/tests-ng/update-ignore-relative.sh index 09d4021..d2302d5 100755 --- a/tests-ng/update-ignore-relative.sh +++ b/tests-ng/update-ignore-relative.sh @@ -7,7 +7,7 @@ # # exit on first error -#set -e +set -e # all this crap to get current path rl="readlink -f" diff --git a/tests-ng/update-ignore.sh b/tests-ng/update-ignore.sh index af9e34c..a16e8af 100755 --- a/tests-ng/update-ignore.sh +++ b/tests-ng/update-ignore.sh @@ -7,7 +7,7 @@ # # exit on first error -#set -e +set -e # all this crap to get current path rl="readlink -f"