diff --git a/docs/config-format.md b/docs/config-format.md
index 5d32131..666e5a3 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))
@@ -69,6 +71,7 @@ Entry | Description
- ""
actions:
-
+ template: (true|false)
trans_read:
trans_write:
```
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
diff --git a/dotdrop/cfg_yaml.py b/dotdrop/cfg_yaml.py
index 93dda10..c87f219 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_template = 'template'
# 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_template = Settings.key_template_dotfile_default
# link values
lnk_nolink = LinkTypes.NOLINK.name.lower()
@@ -597,6 +599,11 @@ 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 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
def _add_variables(self, new, shell=False, template=True, prio=False):
diff --git a/dotdrop/dotdrop.py b/dotdrop/dotdrop.py
index 26b4707..729dead 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,
+ 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)
+ actionexec=pre_actions_exec,
+ template=dotfile.template)
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,
+ template=dotfile.template)
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,
+ 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 ae2025c..da7890d 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_template = 'template'
def __init__(self, key, dst, src,
actions=[], trans_r=None, trans_w=None,
link=LinkTypes.NOLINK, noempty=False,
cmpignore=[], upignore=[],
- instignore=[]):
+ instignore=[], template=True):
"""
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
+ @template: template 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.template = template
if self.link != LinkTypes.NOLINK and \
(
@@ -91,6 +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['template'] = value.get(cls.key_template, True)
# remove old entries
value.pop(cls.key_noempty, None)
value.pop(cls.key_trans_r, None)
@@ -104,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(self.template)
+ return msg
def prt(self):
"""extended dotfile to str"""
@@ -114,6 +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{}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 ef0e0a9..6f1cb73 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=[], template=True):
"""
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
+ @template: template this dotfile
return
- True, None : success
@@ -103,22 +105,25 @@ 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,
+ template=template)
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,
+ template=template)
return self._log_install(b, e)
- def link(self, templater, src, dst, actionexec=None):
+ 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
+ @template: template this dotfile
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,
+ template=template)
return self._log_install(b, e)
- if 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)
+ i, err = self.install(templater, src, tmp, actionexec=actionexec,
+ template=template)
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,
+ 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
@actionexec: action executor callback
+ @template: template this dotfile
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 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)
+ r, e = self.install(templater, src, tmp, actionexec=actionexec,
+ template=template)
if not r and e and not os.path.exists(tmp):
continue
src = tmp
@@ -264,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)
@@ -306,14 +316,16 @@ 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=[], template=True):
"""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('template: {}'.format(template))
+ self.log.dbg('no empty: {}'.format(noempty))
if utils.must_ignore([src, dst], ignore, debug=self.debug):
if self.debug:
@@ -324,42 +336,55 @@ 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)
+
+ # handle the file
+ content = None
+ if template:
+ # 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
+ 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:
self.log.sub('copied {} to {}'.format(src, dst))
return True, None
+ # error
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=[], template=True):
"""install src to dst when is a directory"""
if self.debug:
self.log.dbg('install dir {}'.format(src))
@@ -374,11 +399,12 @@ 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,
+ template=template)
if not res and err:
# error occured
ret = res, err
@@ -388,11 +414,12 @@ 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,
+ template=template)
if not res and err:
# error occured
ret = res, err
@@ -403,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
@@ -427,15 +463,26 @@ 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:
+ # get diff
+ diff = self._diff_before_write(src, dst,
+ content=content,
+ quiet=True)
+ 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
@@ -450,24 +497,39 @@ 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
+ using a temp file if content is not None
+ returns diff string ('' if same)
+ """
tmp = None
if content:
tmp = utils.write_to_tmpfile(content)
@@ -477,9 +539,12 @@ class Installer:
if tmp:
utils.remove(tmp, quiet=True)
- # fake the output for readability
- if not diff:
- return
+ 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)
@@ -530,12 +595,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, template=True):
"""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, template=template)
+ return r, tmpdst
- def install_to_temp(self, templater, tmpdir, src, dst):
+ def install_to_temp(self, templater, tmpdir, src, dst, template=True):
"""install a dotfile to a tempdir"""
ret = False
tmpdst = ''
@@ -553,7 +619,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,
+ 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 264ccc2..64d0853 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=True):
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)
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}'
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 9651338..fd62890 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/notemplate.sh b/tests-ng/notemplate.sh
new file mode 100755
index 0000000..4e5a72c
--- /dev/null
+++ b/tests-ng/notemplate.sh
@@ -0,0 +1,286 @@
+#!/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: false
+dotfiles:
+ f_f1:
+ dst: ${tmpd}/f1
+ src: f1
+ d_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
+ f_fn:
+ dst: ${tmpd}/fn
+ src: fn
+ template: true
+profiles:
+ p1:
+ dotfiles:
+ - f_f1
+ - d_d1
+ - d_d2
+ - d_d3
+ - f_fl
+ - f_fn
+_EOF
+#cat ${cfg}
+
+# 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
+
+# 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
+
+# create the normal dotfile
+echo "before" > ${tmps}/dotfiles/fn
+echo "{#@@ should not be stripped @@#}" >> ${tmps}/dotfiles/fn
+echo "after" >> ${tmps}/dotfiles/fn
+
+# install
+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"
+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)
+
+# 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
+
+# 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:
+ backup: true
+ create: true
+ dotpath: dotfiles
+ template_dotfile_default: true
+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:
+ - f_f1
+ - d_d1
+ - d_d2
+ - d_d3
+ - f_fl
+ - f_fn
+_EOF
+#cat ${cfg}
+
+# clean destination
+rm -rf ${tmpd}/*
+
+# install
+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"
+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)
+
+# 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}
+
+echo "OK"
+exit 0
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"